From 35bd883d40783364e0637ef7eab96d3817f0f43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Delafond?= Date: Sat, 4 Oct 2014 17:09:23 +0200 Subject: [PATCH 1/2] Imported Upstream version 0.8 --- src/github.com/smira/aptly/.travis.yml | 7 +- src/github.com/smira/aptly/AUTHORS | 3 +- src/github.com/smira/aptly/Gomfile | 9 +- src/github.com/smira/aptly/Makefile | 1 + src/github.com/smira/aptly/README.rst | 10 +- .../src/github.com/jlaffaye/ftp/LICENSE | 13 + .../src/github.com/jlaffaye/ftp/README.md | 7 + .../github.com/jlaffaye/ftp/client_test.go | 132 + .../src/github.com/jlaffaye/ftp/ftp.go | 491 ++ .../src/github.com/jlaffaye/ftp/parse_test.go | 63 + .../src/github.com/jlaffaye/ftp/status.go | 110 + .../mattn/go-shellwords/.travis.yml | 9 + .../github.com/mattn/go-shellwords/README.md | 40 + .../mattn/go-shellwords/shellwords.go | 129 + .../mattn/go-shellwords/shellwords_test.go | 132 + .../mattn/go-shellwords/util_posix.go | 19 + .../mattn/go-shellwords/util_windows.go | 17 + .../src/github.com/mitchellh/goamz/README.md | 39 +- .../goamz/autoscaling/autoscaling.go | 31 +- .../goamz/autoscaling/autoscaling_test.go | 8 +- .../src/github.com/mitchellh/goamz/aws/aws.go | 75 + .../mitchellh/goamz/aws/aws_test.go | 115 + .../src/github.com/mitchellh/goamz/ec2/ec2.go | 273 +- .../mitchellh/goamz/ec2/ec2_test.go | 67 +- .../mitchellh/goamz/ec2/responses_test.go | 143 +- .../src/github.com/mitchellh/goamz/elb/elb.go | 172 +- .../mitchellh/goamz/elb/elb_test.go | 79 + .../mitchellh/goamz/elb/responses_test.go | 44 + .../mitchellh/goamz/exp/sdb/sign.go | 5 +- .../mitchellh/goamz/exp/sns/permissions.go | 51 + .../github.com/mitchellh/goamz/exp/sns/sns.go | 341 +- .../mitchellh/goamz/exp/sns/sns_test.go | 15 +- .../mitchellh/goamz/exp/sns/subscription.go | 161 + .../mitchellh/goamz/exp/sns/topic.go | 103 + .../src/github.com/mitchellh/goamz/rds/rds.go | 273 +- .../mitchellh/goamz/rds/rds_test.go | 117 + .../mitchellh/goamz/rds/responses_test.go | 221 + .../mitchellh/goamz/route53/responses_test.go | 26 + .../mitchellh/goamz/route53/route53.go | 62 +- .../mitchellh/goamz/route53/route53_test.go | 19 + .../src/github.com/mitchellh/goamz/s3/s3.go | 18 +- .../github.com/mitchellh/goamz/s3/s3_test.go | 6 + .../src/github.com/mitchellh/goamz/s3/sign.go | 10 +- .../mitchellh/goamz/s3/sign_test.go | 43 + .../_vendor/src/github.com/smira/flag/flag.go | 11 + .../smira/go-ftp-protocol/.gitignore | 23 + .../github.com/smira/go-ftp-protocol/LICENSE | 21 + .../smira/go-ftp-protocol/README.md | 23 + .../go-ftp-protocol/protocol/protocol.go | 152 + .../go-ftp-protocol/protocol/protocol_test.go | 94 + .../github.com/syndtr/goleveldb/.travis.yml | 8 + .../src/github.com/syndtr/goleveldb/README.md | 15 +- .../syndtr/goleveldb/leveldb/bench_test.go | 15 +- .../syndtr/goleveldb/leveldb/cache/cache.go | 152 +- .../goleveldb/leveldb/cache/cache_test.go | 553 ++- .../goleveldb/leveldb/cache/empty_cache.go | 246 - .../goleveldb/leveldb/cache/lru_cache.go | 628 ++- .../syndtr/goleveldb/leveldb/comparer.go | 52 +- .../syndtr/goleveldb/leveldb/corrupt_test.go | 58 + .../github.com/syndtr/goleveldb/leveldb/db.go | 489 +- .../syndtr/goleveldb/leveldb/db_compaction.go | 514 +- .../syndtr/goleveldb/leveldb/db_iter.go | 64 +- .../syndtr/goleveldb/leveldb/db_snapshot.go | 60 +- .../syndtr/goleveldb/leveldb/db_state.go | 198 +- .../syndtr/goleveldb/leveldb/db_test.go | 208 +- .../syndtr/goleveldb/leveldb/db_util.go | 54 +- .../syndtr/goleveldb/leveldb/db_write.go | 196 +- .../syndtr/goleveldb/leveldb/doc.go | 10 + .../syndtr/goleveldb/leveldb/error.go | 38 + .../syndtr/goleveldb/leveldb/external_test.go | 11 +- .../goleveldb/leveldb/go13_bench_test.go | 58 + .../goleveldb/leveldb/iterator/array_iter.go | 30 +- .../leveldb/iterator/indexed_iter.go | 36 +- .../syndtr/goleveldb/leveldb/iterator/iter.go | 27 +- .../goleveldb/leveldb/iterator/merged_iter.go | 14 +- .../goleveldb/leveldb/journal/journal.go | 121 +- .../goleveldb/leveldb/journal/journal_test.go | 490 ++ .../syndtr/goleveldb/leveldb/key_test.go | 6 +- .../syndtr/goleveldb/leveldb/memdb/memdb.go | 32 +- .../goleveldb/leveldb/memdb/memdb_test.go | 2 +- .../syndtr/goleveldb/leveldb/opt/options.go | 46 +- .../syndtr/goleveldb/leveldb/options.go | 4 +- .../syndtr/goleveldb/leveldb/session.go | 177 +- .../goleveldb/leveldb/session_record.go | 52 +- .../syndtr/goleveldb/leveldb/session_util.go | 27 +- .../goleveldb/leveldb/storage/file_storage.go | 14 + .../leveldb/storage/file_storage_plan9.go | 12 + .../leveldb/storage/file_storage_solaris.go | 68 + .../leveldb/storage/file_storage_unix.go | 14 +- .../leveldb/storage/file_storage_windows.go | 2 + .../goleveldb/leveldb/storage/storage.go | 7 +- .../syndtr/goleveldb/leveldb/storage_test.go | 2 +- .../syndtr/goleveldb/leveldb/table.go | 317 +- .../goleveldb/leveldb/table/block_test.go | 4 +- .../syndtr/goleveldb/leveldb/table/reader.go | 369 +- .../goleveldb/leveldb/table/table_test.go | 14 +- .../goleveldb/leveldb/testutil/kvtest.go | 100 +- .../goleveldb/leveldb/testutil/storage.go | 5 + .../syndtr/goleveldb/leveldb/testutil_test.go | 1 + .../goleveldb/leveldb/util/buffer_pool.go | 221 + .../syndtr/goleveldb/leveldb/util/pool.go | 21 + .../goleveldb/leveldb/util/pool_legacy.go | 33 + .../syndtr/goleveldb/leveldb/util/range.go | 16 + .../syndtr/goleveldb/leveldb/util/util.go | 33 +- .../syndtr/goleveldb/leveldb/version.go | 293 +- .../src/github.com/vaughan0/go-ini/LICENSE | 14 + .../src/github.com/vaughan0/go-ini/README.md | 70 + .../src/github.com/vaughan0/go-ini/ini.go | 123 + .../vaughan0/go-ini/ini_linux_test.go | 43 + .../github.com/vaughan0/go-ini/ini_test.go | 89 + .../src/github.com/vaughan0/go-ini/test.ini | 2 + .../smira/aptly/aptly/interfaces.go | 2 + src/github.com/smira/aptly/aptly/version.go | 2 +- src/github.com/smira/aptly/cmd/cmd.go | 19 +- src/github.com/smira/aptly/cmd/context.go | 78 +- src/github.com/smira/aptly/cmd/mirror.go | 3 +- .../smira/aptly/cmd/mirror_create.go | 9 +- src/github.com/smira/aptly/cmd/mirror_drop.go | 5 + src/github.com/smira/aptly/cmd/mirror_edit.go | 30 +- .../smira/aptly/cmd/mirror_rename.go | 5 + .../smira/aptly/cmd/mirror_search.go | 26 + src/github.com/smira/aptly/cmd/mirror_show.go | 9 + .../smira/aptly/cmd/mirror_update.go | 126 +- src/github.com/smira/aptly/cmd/package.go | 16 + .../smira/aptly/cmd/package_search.go | 48 + .../smira/aptly/cmd/package_show.go | 137 + src/github.com/smira/aptly/cmd/publish.go | 3 +- .../smira/aptly/cmd/publish_list.go | 2 +- .../smira/aptly/cmd/publish_repo.go | 2 + .../smira/aptly/cmd/publish_snapshot.go | 2 + .../smira/aptly/cmd/publish_switch.go | 2 + .../smira/aptly/cmd/publish_update.go | 2 + src/github.com/smira/aptly/cmd/repo.go | 1 + src/github.com/smira/aptly/cmd/repo_add.go | 32 +- src/github.com/smira/aptly/cmd/repo_edit.go | 2 +- src/github.com/smira/aptly/cmd/repo_search.go | 26 + src/github.com/smira/aptly/cmd/run.go | 44 + src/github.com/smira/aptly/cmd/snapshot.go | 2 + .../smira/aptly/cmd/snapshot_create.go | 5 + .../smira/aptly/cmd/snapshot_filter.go | 107 + .../smira/aptly/cmd/snapshot_search.go | 125 + src/github.com/smira/aptly/cmd/task.go | 15 + src/github.com/smira/aptly/cmd/task_run.go | 148 + .../smira/aptly/database/leveldb.go | 32 +- .../smira/aptly/database/leveldb_test.go | 20 + src/github.com/smira/aptly/deb/index_files.go | 254 + src/github.com/smira/aptly/deb/list.go | 70 +- src/github.com/smira/aptly/deb/package.go | 13 + .../smira/aptly/deb/package_collection.go | 51 + .../smira/aptly/deb/package_test.go | 41 + src/github.com/smira/aptly/deb/publish.go | 272 +- .../smira/aptly/deb/publish_test.go | 15 +- src/github.com/smira/aptly/deb/query.go | 67 +- src/github.com/smira/aptly/deb/reflist.go | 8 + .../smira/aptly/deb/reflist_test.go | 13 + src/github.com/smira/aptly/deb/remote.go | 174 +- src/github.com/smira/aptly/deb/remote_test.go | 123 +- .../smira/aptly/deb/snapshot_test.go | 8 +- src/github.com/smira/aptly/http/download.go | 27 +- src/github.com/smira/aptly/http/fake.go | 4 + src/github.com/smira/aptly/http/http.go | 2 +- src/github.com/smira/aptly/main.go | 30 +- src/github.com/smira/aptly/man/aptly.1 | 240 +- .../smira/aptly/man/aptly.1.ronn.tmpl | 28 +- src/github.com/smira/aptly/s3/public.go | 47 +- src/github.com/smira/aptly/s3/public_test.go | 25 +- .../aptly/system/files/aptly_passphrase.pub | Bin 0 -> 915 bytes .../aptly/system/files/aptly_passphrase.sec | Bin 0 -> 1052 bytes src/github.com/smira/aptly/system/lib.py | 7 +- src/github.com/smira/aptly/system/run.py | 4 +- src/github.com/smira/aptly/system/s3_lib.py | 78 + .../aptly/system/t01_version/VersionTest_gold | 2 +- .../aptly/system/t03_help/MainHelpTest_gold | 4 +- .../smira/aptly/system/t03_help/MainTest_gold | 1 + .../system/t03_help/MirrorCreateHelpTest_gold | 3 +- .../system/t03_help/MirrorCreateTest_gold | 1 + .../aptly/system/t03_help/MirrorHelpTest_gold | 3 +- .../aptly/system/t03_help/MirrorTest_gold | 3 +- .../aptly/system/t03_help/WrongFlagTest_gold | 1 + .../t04_mirror/CreateMirror11Test_mirror_show | 1 + .../t04_mirror/CreateMirror13Test_mirror_show | 1 + .../t04_mirror/CreateMirror14Test_mirror_show | 1 + .../t04_mirror/CreateMirror17Test_mirror_show | 1 + .../t04_mirror/CreateMirror18Test_mirror_show | 1 + .../t04_mirror/CreateMirror19Test_mirror_show | 3 +- .../t04_mirror/CreateMirror1Test_mirror_show | 1 + .../system/t04_mirror/CreateMirror20Test_gold | 2 +- .../t04_mirror/CreateMirror21Test_mirror_show | 3 +- .../t04_mirror/CreateMirror22Test_mirror_show | 3 +- .../system/t04_mirror/CreateMirror24Test_gold | 6 + .../system/t04_mirror/CreateMirror25Test_gold | 4 + .../t04_mirror/CreateMirror25Test_mirror_show | 20 + .../system/t04_mirror/CreateMirror26Test_gold | 1 + .../t04_mirror/CreateMirror2Test_mirror_show | 1 + .../t04_mirror/CreateMirror3Test_mirror_show | 1 + .../t04_mirror/CreateMirror7Test_mirror_show | 1 + .../t04_mirror/CreateMirror9Test_mirror_show | 3 +- .../system/t04_mirror/EditMirror1Test_gold | 2 +- .../t04_mirror/EditMirror1Test_mirror_show | 4 +- .../t04_mirror/EditMirror3Test_mirror_show | 2 +- .../t04_mirror/EditMirror5Test_mirror_show | 3 +- .../system/t04_mirror/EditMirror6Test_gold | 2 + .../t04_mirror/EditMirror6Test_mirror_show | 20 + .../system/t04_mirror/EditMirror7Test_gold | 2 + .../system/t04_mirror/EditMirror8Test_gold | 1 + .../t04_mirror/EditMirror8Test_mirror_show | 20 + .../system/t04_mirror/EditMirror9Test_gold | 1 + .../system/t04_mirror/SearchMirror1Test_gold | 4043 ++++++++++++++++ .../system/t04_mirror/SearchMirror2Test_gold | 1 + .../system/t04_mirror/SearchMirror3Test_gold | 1 + .../system/t04_mirror/SearchMirror4Test_gold | 95 + .../system/t04_mirror/ShowMirror1Test_gold | 1 + .../system/t04_mirror/ShowMirror3Test_gold | 2 +- .../system/t04_mirror/ShowMirror4Test_gold | 3 +- .../system/t04_mirror/UpdateMirror11Test_gold | 21 + .../system/t04_mirror/UpdateMirror12Test_gold | 33 + .../smira/aptly/system/t04_mirror/__init__.py | 1 + .../smira/aptly/system/t04_mirror/create.py | 33 + .../smira/aptly/system/t04_mirror/edit.py | 47 +- .../smira/aptly/system/t04_mirror/search.py | 36 + .../smira/aptly/system/t04_mirror/update.py | 32 + .../t05_snapshot/FilterSnapshot1Test_gold | 5 + .../FilterSnapshot1Test_snapshot_show | 8 + .../t05_snapshot/FilterSnapshot2Test_gold | 5 + .../FilterSnapshot2Test_snapshot_show | 13 + .../t05_snapshot/FilterSnapshot3Test_gold | 5 + .../FilterSnapshot3Test_snapshot_show | 127 + .../t05_snapshot/FilterSnapshot5Test_gold | 1 + .../t05_snapshot/FilterSnapshot6Test_gold | 3 + .../t05_snapshot/FilterSnapshot7Test_gold | 5 + .../FilterSnapshot7Test_snapshot_show | 14 + .../t05_snapshot/SearchSnapshot1Test_gold | 4043 ++++++++++++++++ .../t05_snapshot/SearchSnapshot2Test_gold | 1 + .../t05_snapshot/SearchSnapshot3Test_gold | 1 + .../t05_snapshot/SearchSnapshot4Test_gold | 95 + .../t05_snapshot/VerifySnapshot4Test_gold | 7 +- .../t05_snapshot/VerifySnapshot5Test_gold | 7 +- .../t05_snapshot/VerifySnapshot6Test_gold | 18 +- .../aptly/system/t05_snapshot/__init__.py | 2 + .../smira/aptly/system/t05_snapshot/filter.py | 98 + .../smira/aptly/system/t05_snapshot/search.py | 39 + .../system/t06_publish/PublishRepo12Test_gold | 1 + .../system/t06_publish/PublishRepo14Test_gold | 1 + .../system/t06_publish/PublishRepo15Test_gold | 1 + .../system/t06_publish/PublishRepo16Test_gold | 1 + .../system/t06_publish/PublishRepo17Test_gold | 1 + .../system/t06_publish/PublishRepo18Test_gold | 1 + .../system/t06_publish/PublishRepo1Test_gold | 1 + .../system/t06_publish/PublishRepo25Test_gold | 1 + .../system/t06_publish/PublishRepo26Test_gold | 14 + .../t06_publish/PublishRepo26Test_udeb_binary | 20 + .../system/t06_publish/PublishRepo27Test_gold | 14 + .../t06_publish/PublishRepo27Test_udeb_binary | 20 + .../system/t06_publish/PublishRepo2Test_gold | 1 + .../system/t06_publish/PublishRepo3Test_gold | 1 + .../system/t06_publish/PublishRepo4Test_gold | 1 + .../t06_publish/PublishSnapshot13Test_gold | 1 + .../t06_publish/PublishSnapshot15Test_gold | 1 + .../t06_publish/PublishSnapshot16Test_gold | 1 + .../t06_publish/PublishSnapshot17Test_gold | 1 + .../t06_publish/PublishSnapshot19Test_gold | 1 + .../t06_publish/PublishSnapshot1Test_gold | 1 + .../PublishSnapshot1Test_packages_amd64 | 109 + .../PublishSnapshot1Test_packages_i386 | 109 + .../t06_publish/PublishSnapshot20Test_gold | 1 + .../t06_publish/PublishSnapshot22Test_gold | 1 + .../t06_publish/PublishSnapshot23Test_gold | 1 + .../t06_publish/PublishSnapshot24Test_gold | 1 + .../t06_publish/PublishSnapshot25Test_gold | 1 + .../t06_publish/PublishSnapshot26Test_gold | 1 + .../t06_publish/PublishSnapshot27Test_gold | 1 + .../t06_publish/PublishSnapshot2Test_gold | 1 + .../t06_publish/PublishSnapshot34Test_gold | 1 + .../t06_publish/PublishSnapshot35Test_gold | 13 + .../PublishSnapshot35Test_packages_amd64 | 88 + .../PublishSnapshot35Test_packages_i386 | 88 + .../PublishSnapshot35Test_packages_udeb_amd64 | 40 + .../PublishSnapshot35Test_packages_udeb_i386 | 40 + .../t06_publish/PublishSnapshot35Test_release | 58 + .../PublishSnapshot35Test_release_udeb_i386 | 5 + .../t06_publish/PublishSnapshot3Test_gold | 1 + .../t06_publish/PublishSnapshot4Test_gold | 1 + .../t06_publish/PublishSnapshot5Test_gold | 1 + .../t06_publish/PublishSwitch11Test_gold | 1 + .../t06_publish/PublishSwitch1Test_gold | 1 + .../t06_publish/PublishSwitch2Test_gold | 1 + .../t06_publish/PublishSwitch3Test_gold | 1 + .../t06_publish/PublishSwitch4Test_gold | 1 + .../t06_publish/PublishSwitch8Test_gold | 1 + .../t06_publish/PublishUpdate10Test_gold | 1 + .../t06_publish/PublishUpdate1Test_gold | 1 + .../t06_publish/PublishUpdate2Test_gold | 1 + .../t06_publish/PublishUpdate3Test_gold | 1 + .../t06_publish/PublishUpdate4Test_gold | 1 + .../t06_publish/PublishUpdate7Test_gold | 1 + .../t06_publish/PublishUpdate8Test_gold | 1 + .../system/t06_publish/S3Publish1Test_binary | 25 + .../system/t06_publish/S3Publish1Test_gold | 13 + .../system/t06_publish/S3Publish1Test_release | 34 + .../system/t06_publish/S3Publish1Test_sources | 42 + .../system/t06_publish/S3Publish2Test_binary | 25 + .../system/t06_publish/S3Publish2Test_gold | 8 + .../system/t06_publish/S3Publish2Test_release | 34 + .../system/t06_publish/S3Publish2Test_sources | 0 .../system/t06_publish/S3Publish3Test_binary | 29 + .../system/t06_publish/S3Publish3Test_gold | 8 + .../system/t06_publish/S3Publish3Test_release | 34 + .../system/t06_publish/S3Publish4Test_gold | 4 + .../system/t06_publish/S3Publish5Test_gold | 3 + .../aptly/system/t06_publish/__init__.py | 1 + .../smira/aptly/system/t06_publish/repo.py | 62 + .../smira/aptly/system/t06_publish/s3.py | 158 + .../aptly/system/t06_publish/snapshot.py | 104 + .../pyspi_0.6.1-1.3.conflict.dsc | 12 + .../AddRepo11Test/pyspi_0.6.1.orig.tar.gz | 0 .../aptly/system/t09_repo/AddRepo11Test_gold | 3 + .../system/t09_repo/AddRepo11Test_repo_show | 7 + .../aptly/system/t09_repo/AddRepo12Test_gold | 2 + .../system/t09_repo/AddRepo12Test_repo_show | 7 + .../aptly/system/t09_repo/AddRepo13Test_gold | 6 + .../system/t09_repo/AddRepo13Test_repo_show | 11 + .../system/t09_repo/SearchRepo1Test_gold | 4043 ++++++++++++++++ .../system/t09_repo/SearchRepo2Test_gold | 1 + .../system/t09_repo/SearchRepo3Test_gold | 1 + .../system/t09_repo/SearchRepo4Test_gold | 95 + .../smira/aptly/system/t09_repo/__init__.py | 1 + .../smira/aptly/system/t09_repo/add.py | 51 + .../smira/aptly/system/t09_repo/search.py | 39 + .../aptly/system/t10_task/RunTask1Test_gold | 26 + .../aptly/system/t10_task/RunTask2Test_gold | 22 + .../aptly/system/t10_task/RunTask3Test_gold | 9 + .../aptly/system/t10_task/RunTask4Test/task | 3 + .../aptly/system/t10_task/RunTask4Test_gold | 26 + .../aptly/system/t10_task/RunTask5Test_gold | 2 + .../smira/aptly/system/t10_task/__init__.py | 6 + .../smira/aptly/system/t10_task/run.py | 38 + .../t11_package/SearchPackage1Test_gold | 4309 +++++++++++++++++ .../t11_package/SearchPackage2Test_gold | 0 .../t11_package/SearchPackage3Test_gold | 1 + .../t11_package/SearchPackage4Test_gold | 4 + .../system/t11_package/ShowPackage1Test_gold | 163 + .../system/t11_package/ShowPackage2Test_gold | 0 .../system/t11_package/ShowPackage3Test_gold | 21 + .../system/t11_package/ShowPackage4Test_gold | 24 + .../system/t11_package/ShowPackage5Test_gold | 25 + .../system/t11_package/ShowPackage6Test_gold | 29 + .../aptly/system/t11_package/__init__.py | 6 + .../smira/aptly/system/t11_package/search.py | 34 + .../smira/aptly/system/t11_package/show.py | 62 + .../dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb | Bin 0 -> 11806 bytes .../dmraid-udeb_1.0.0.rc16-4.1_i386.udeb | Bin 0 -> 11022 bytes src/github.com/smira/aptly/utils/config.go | 15 +- .../smira/aptly/utils/config_test.go | 5 +- src/github.com/smira/aptly/utils/gpg.go | 19 +- 354 files changed, 29778 insertions(+), 2868 deletions(-) create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/client_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/ftp.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/parse_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/status.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/.travis.yml create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_posix.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_windows.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/permissions.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/subscription.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/topic.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/.gitignore create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/.travis.yml delete mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/empty_cache.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/go13_bench_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool_legacy.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_linux_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/test.ini create mode 100644 src/github.com/smira/aptly/cmd/mirror_search.go create mode 100644 src/github.com/smira/aptly/cmd/package.go create mode 100644 src/github.com/smira/aptly/cmd/package_search.go create mode 100644 src/github.com/smira/aptly/cmd/package_show.go create mode 100644 src/github.com/smira/aptly/cmd/repo_search.go create mode 100644 src/github.com/smira/aptly/cmd/run.go create mode 100644 src/github.com/smira/aptly/cmd/snapshot_filter.go create mode 100644 src/github.com/smira/aptly/cmd/snapshot_search.go create mode 100644 src/github.com/smira/aptly/cmd/task.go create mode 100644 src/github.com/smira/aptly/cmd/task_run.go create mode 100644 src/github.com/smira/aptly/deb/index_files.go create mode 100644 src/github.com/smira/aptly/system/files/aptly_passphrase.pub create mode 100644 src/github.com/smira/aptly/system/files/aptly_passphrase.sec create mode 100644 src/github.com/smira/aptly/system/s3_lib.py create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror24Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror26Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror7Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_mirror_show create mode 100644 src/github.com/smira/aptly/system/t04_mirror/EditMirror9Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/SearchMirror1Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/SearchMirror2Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/SearchMirror3Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/SearchMirror4Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/UpdateMirror12Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/search.py create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_snapshot_show create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_snapshot_show create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_snapshot_show create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot5Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot6Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_snapshot_show create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot1Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot2Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot3Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot4Test_gold create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/filter.py create mode 100644 src/github.com/smira/aptly/system/t05_snapshot/search.py create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_udeb_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_udeb_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_amd64 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_i386 create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_sources create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_sources create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish4Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/S3Publish5Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/s3.py create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1-1.3.conflict.dsc create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1.orig.tar.gz create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_repo_show create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_repo_show create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_repo_show create mode 100644 src/github.com/smira/aptly/system/t09_repo/SearchRepo1Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/SearchRepo2Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/SearchRepo3Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/SearchRepo4Test_gold create mode 100644 src/github.com/smira/aptly/system/t09_repo/search.py create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask2Test_gold create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask3Test_gold create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask4Test/task create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask4Test_gold create mode 100644 src/github.com/smira/aptly/system/t10_task/RunTask5Test_gold create mode 100644 src/github.com/smira/aptly/system/t10_task/__init__.py create mode 100644 src/github.com/smira/aptly/system/t10_task/run.py create mode 100644 src/github.com/smira/aptly/system/t11_package/SearchPackage1Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/SearchPackage3Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/SearchPackage4Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage1Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage2Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage3Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage4Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage5Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/ShowPackage6Test_gold create mode 100644 src/github.com/smira/aptly/system/t11_package/__init__.py create mode 100644 src/github.com/smira/aptly/system/t11_package/search.py create mode 100644 src/github.com/smira/aptly/system/t11_package/show.py create mode 100644 src/github.com/smira/aptly/system/udebs/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb create mode 100644 src/github.com/smira/aptly/system/udebs/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb diff --git a/src/github.com/smira/aptly/.travis.yml b/src/github.com/smira/aptly/.travis.yml index bf949649..26b65e68 100644 --- a/src/github.com/smira/aptly/.travis.yml +++ b/src/github.com/smira/aptly/.travis.yml @@ -2,13 +2,16 @@ language: go go: - 1.2.1 - - 1.3 + - 1.3.1 - tip env: global: - secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc=" - + - secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E=" +before_install: + - sudo apt-get update -qq + - sudo apt-get install -y python-boto install: - make prepare diff --git a/src/github.com/smira/aptly/AUTHORS b/src/github.com/smira/aptly/AUTHORS index d8e8ad09..17fdfe01 100644 --- a/src/github.com/smira/aptly/AUTHORS +++ b/src/github.com/smira/aptly/AUTHORS @@ -3,4 +3,5 @@ List of contributors, in chronological order: * Andrey Smirnov (https://github.com/smira) * Sebastien Binet (https://github.com/sbinet) * Ryan Uber (https://github.com/ryanuber) -* Simon Aquino (https://github.com/simonaquino) \ No newline at end of file +* Simon Aquino (https://github.com/simonaquino) +* Vincent Batoufflet (https://github.com/vbatoufflet) \ No newline at end of file diff --git a/src/github.com/smira/aptly/Gomfile b/src/github.com/smira/aptly/Gomfile index 25920026..910bce2b 100644 --- a/src/github.com/smira/aptly/Gomfile +++ b/src/github.com/smira/aptly/Gomfile @@ -4,11 +4,14 @@ gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2' gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556' gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793' gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827' -gom 'github.com/mitchellh/goamz/s3', :commit => '55f224c07975fddef9d2116600c664e30df3d594' +gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1' +gom 'github.com/mattn/go-shellwords', :commit => 'c7ca6f94add751566a61cf2199e1de78d4c3eee4' +gom 'github.com/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405' gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b' gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47' -gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841' -gom 'github.com/syndtr/goleveldb/leveldb', :commit => '9888007' +gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107' +gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa' +gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'e2fa4e6ac1cc41a73bc9fd467878ecbf65df5cc3' gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b' gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08' diff --git a/src/github.com/smira/aptly/Makefile b/src/github.com/smira/aptly/Makefile index afd1c520..1eccbc3c 100644 --- a/src/github.com/smira/aptly/Makefile +++ b/src/github.com/smira/aptly/Makefile @@ -79,5 +79,6 @@ src-package: rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin} tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION) rm -rf aptly-$(VERSION) + curl -T aptly-$(VERSION)-src.tar.bz2 -usmira:$(BINTRAY_KEY) https://api.bintray.com/content/smira/aptly/aptly/$(VERSION)/$(VERSION)/aptly-$(VERSION)-src.tar.bz2 .PHONY: coverage.out diff --git a/src/github.com/smira/aptly/README.rst b/src/github.com/smira/aptly/README.rst index e68f8277..be90bb6c 100644 --- a/src/github.com/smira/aptly/README.rst +++ b/src/github.com/smira/aptly/README.rst @@ -11,9 +11,11 @@ aptly .. image:: http://gobuild.io/badge/github.com/smira/aptly/download.png :target: http://gobuild.io/github.com/smira/aptly - Aptly is a swiss army knife for Debian repository management. +.. image:: http://www.aptly.info/img/aptly_logo.png + :target: http://www.aptly.info/ + Documentation is available at `http://www.aptly.info/ `_. For support use mailing list `aptly-discuss `_. @@ -24,14 +26,14 @@ Aptly features: ("+" means planned features) * publish snapshot as Debian repository, ready to be consumed by apt * controlled update of one or more packages in snapshot from upstream mirror, tracking dependencies * merge two or more snapshots into one -* filter repository by search query, pulling dependencies when required (+) -* publish self-made packages as Debian repositories (+) +* filter repository by search query, pulling dependencies when required +* publish self-made packages as Debian repositories * mirror repositories "as-is" (without resigning with user's key) (+) * support for yum repositories (+) Current limitations: -* debian-installer and translations not supported yet +* translations are not supported yet Download -------- diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/LICENSE new file mode 100644 index 00000000..9ab085c5 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2011-2013, Julien Laffaye + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/README.md new file mode 100644 index 00000000..8cfe8dc6 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/README.md @@ -0,0 +1,7 @@ +# goftp # + +A FTP client package for Go + +## Documentation ## + +http://godoc.org/github.com/jlaffaye/ftp diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/client_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/client_test.go new file mode 100644 index 00000000..d93d310d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/client_test.go @@ -0,0 +1,132 @@ +package ftp + +import ( + "bytes" + "io/ioutil" + "testing" +) + +const ( + testData = "Just some text" + testDir = "mydir" +) + +func TestConn(t *testing.T) { + c, err := Connect("localhost:21") + if err != nil { + t.Fatal(err) + } + + err = c.Login("anonymous", "anonymous") + if err != nil { + t.Fatal(err) + } + + err = c.NoOp() + if err != nil { + t.Error(err) + } + + data := bytes.NewBufferString(testData) + err = c.Stor("test", data) + if err != nil { + t.Error(err) + } + + _, err = c.List(".") + if err != nil { + t.Error(err) + } + + err = c.Rename("test", "tset") + if err != nil { + t.Error(err) + } + + r, err := c.Retr("tset") + if err != nil { + t.Error(err) + } else { + buf, err := ioutil.ReadAll(r) + if err != nil { + t.Error(err) + } + if string(buf) != testData { + t.Errorf("'%s'", buf) + } + r.Close() + } + + r, err = c.Retr("tset") + if err != nil { + t.Error(err) + } else { + r.Close() + } + + err = c.Delete("tset") + if err != nil { + t.Error(err) + } + + err = c.MakeDir(testDir) + if err != nil { + t.Error(err) + } + + err = c.ChangeDir(testDir) + if err != nil { + t.Error(err) + } + + dir, err := c.CurrentDir() + if err != nil { + t.Error(err) + } else { + if dir != "/"+testDir { + t.Error("Wrong dir: " + dir) + } + } + + err = c.ChangeDirToParent() + if err != nil { + t.Error(err) + } + + err = c.RemoveDir(testDir) + if err != nil { + t.Error(err) + } + + err = c.Logout() + if err != nil { + t.Error(err) + } + + c.Quit() + + err = c.NoOp() + if err == nil { + t.Error("Expected error") + } +} + +// ftp.mozilla.org uses multiline 220 response +func TestConn2(t *testing.T) { + c, err := Connect("ftp.mozilla.org:21") + if err != nil { + t.Fatal(err) + } + + err = c.Login("anonymous", "anonymous") + if err != nil { + t.Fatal(err) + } + + _, err = c.List(".") + if err != nil { + t.Error(err) + } + + c.Quit() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/ftp.go b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/ftp.go new file mode 100644 index 00000000..24e04500 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/ftp.go @@ -0,0 +1,491 @@ +// Package ftp implements a FTP client as described in RFC 959. +package ftp + +import ( + "bufio" + "errors" + "fmt" + "io" + "net" + "net/textproto" + "strconv" + "strings" + "time" +) + +// EntryType describes the different types of an Entry. +type EntryType int + +const ( + EntryTypeFile EntryType = iota + EntryTypeFolder + EntryTypeLink +) + +// ServerConn represents the connection to a remote FTP server. +type ServerConn struct { + conn *textproto.Conn + host string + features map[string]string +} + +// Entry describes a file and is returned by List(). +type Entry struct { + Name string + Type EntryType + Size uint64 + Time time.Time +} + +// response represent a data-connection +type response struct { + conn net.Conn + c *ServerConn +} + +// Connect initializes the connection to the specified ftp server address. +// +// It is generally followed by a call to Login() as most FTP commands require +// an authenticated user. +func Connect(addr string) (*ServerConn, error) { + conn, err := textproto.Dial("tcp", addr) + if err != nil { + return nil, err + } + + a := strings.SplitN(addr, ":", 2) + c := &ServerConn{ + conn: conn, + host: a[0], + features: make(map[string]string), + } + + _, _, err = c.conn.ReadResponse(StatusReady) + if err != nil { + c.Quit() + return nil, err + } + + err = c.feat() + if err != nil { + c.Quit() + return nil, err + } + + return c, nil +} + +// Login authenticates the client with specified user and password. +// +// "anonymous"/"anonymous" is a common user/password scheme for FTP servers +// that allows anonymous read-only accounts. +func (c *ServerConn) Login(user, password string) error { + code, message, err := c.cmd(-1, "USER %s", user) + if err != nil { + return err + } + + switch code { + case StatusLoggedIn: + case StatusUserOK: + _, _, err = c.cmd(StatusLoggedIn, "PASS %s", password) + if err != nil { + return err + } + default: + return errors.New(message) + } + + // Switch to binary mode + _, _, err = c.cmd(StatusCommandOK, "TYPE I") + if err != nil { + return err + } + + return nil +} + +// feat issues a FEAT FTP command to list the additional commands supported by +// the remote FTP server. +// FEAT is described in RFC 2389 +func (c *ServerConn) feat() error { + code, message, err := c.cmd(-1, "FEAT") + if err != nil { + return err + } + + if code != StatusSystem { + // The server does not support the FEAT command. This is not an + // error: we consider that there is no additional feature. + return nil + } + + lines := strings.Split(message, "\n") + for _, line := range lines { + if !strings.HasPrefix(line, " ") { + continue + } + + line = strings.TrimSpace(line) + featureElements := strings.SplitN(line, " ", 2) + + command := featureElements[0] + + var commandDesc string + if len(featureElements) == 2 { + commandDesc = featureElements[1] + } + + c.features[command] = commandDesc + } + + return nil +} + +// epsv issues an "EPSV" command to get a port number for a data connection. +func (c *ServerConn) epsv() (port int, err error) { + _, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV") + if err != nil { + return + } + + start := strings.Index(line, "|||") + end := strings.LastIndex(line, "|") + if start == -1 || end == -1 { + err = errors.New("Invalid EPSV response format") + return + } + port, err = strconv.Atoi(line[start+3 : end]) + return +} + +// pasv issues a "PASV" command to get a port number for a data connection. +func (c *ServerConn) pasv() (port int, err error) { + _, line, err := c.cmd(StatusPassiveMode, "PASV") + if err != nil { + return + } + + // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + start := strings.Index(line, "(") + end := strings.LastIndex(line, ")") + if start == -1 || end == -1 { + err = errors.New("Invalid EPSV response format") + return + } + + // We have to split the response string + pasvData := strings.Split(line[start+1:end], ",") + // Let's compute the port number + portPart1, err1 := strconv.Atoi(pasvData[4]) + if err1 != nil { + err = err1 + return + } + + portPart2, err2 := strconv.Atoi(pasvData[5]) + if err2 != nil { + err = err2 + return + } + + // Recompose port + port = portPart1*256 + portPart2 + return +} + +// openDataConn creates a new FTP data connection. +func (c *ServerConn) openDataConn() (net.Conn, error) { + var port int + var err error + + // If features contains nat6 or EPSV => EPSV + // else -> PASV + _, nat6Supported := c.features["nat6"] + _, epsvSupported := c.features["EPSV"] + if nat6Supported || epsvSupported { + port, err = c.epsv() + if err != nil { + return nil, err + } + } else { + port, err = c.pasv() + if err != nil { + return nil, err + } + } + + // Build the new net address string + addr := fmt.Sprintf("%s:%d", c.host, port) + + conn, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + + return conn, nil +} + +// cmd is a helper function to execute a command and check for the expected FTP +// return code +func (c *ServerConn) cmd(expected int, format string, args ...interface{}) (int, string, error) { + _, err := c.conn.Cmd(format, args...) + if err != nil { + return 0, "", err + } + + code, line, err := c.conn.ReadResponse(expected) + return code, line, err +} + +// cmdDataConn executes a command which require a FTP data connection. +func (c *ServerConn) cmdDataConn(format string, args ...interface{}) (net.Conn, error) { + conn, err := c.openDataConn() + if err != nil { + return nil, err + } + + _, err = c.conn.Cmd(format, args...) + if err != nil { + conn.Close() + return nil, err + } + + code, msg, err := c.conn.ReadCodeLine(-1) + if err != nil { + conn.Close() + return nil, err + } + if code != StatusAlreadyOpen && code != StatusAboutToSend { + conn.Close() + return nil, &textproto.Error{code, msg} + } + + return conn, nil +} + +// parseListLine parses the various non-standard format returned by the LIST +// FTP command. +func parseListLine(line string) (*Entry, error) { + fields := strings.Fields(line) + if len(fields) < 9 { + return nil, errors.New("Unsupported LIST line") + } + + e := &Entry{} + switch fields[0][0] { + case '-': + e.Type = EntryTypeFile + case 'd': + e.Type = EntryTypeFolder + case 'l': + e.Type = EntryTypeLink + default: + return nil, errors.New("Unknown entry type") + } + + if e.Type == EntryTypeFile { + size, err := strconv.ParseUint(fields[4], 10, 0) + if err != nil { + return nil, err + } + e.Size = size + } + var timeStr string + if strings.Contains(fields[7], ":") { // this year + thisYear, _, _ := time.Now().Date() + timeStr = fields[6] + " " + fields[5] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[7] + " GMT" + } else { // not this year + timeStr = fields[6] + " " + fields[5] + " " + fields[7][2:4] + " " + "00:00" + " GMT" + } + t, err := time.Parse("_2 Jan 06 15:04 MST", timeStr) + if err != nil { + return nil, err + } + e.Time = t + + e.Name = strings.Join(fields[8:], " ") + return e, nil +} + +// NameList issues an NLST FTP command. +func (c *ServerConn) NameList(path string) (entries []string, err error) { + conn, err := c.cmdDataConn("NLST %s", path) + if err != nil { + return + } + + r := &response{conn, c} + defer r.Close() + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + entries = append(entries, scanner.Text()) + } + if err = scanner.Err(); err != nil { + return entries, err + } + return +} + +// List issues a LIST FTP command. +func (c *ServerConn) List(path string) (entries []*Entry, err error) { + conn, err := c.cmdDataConn("LIST %s", path) + if err != nil { + return + } + + r := &response{conn, c} + defer r.Close() + + bio := bufio.NewReader(r) + for { + line, e := bio.ReadString('\n') + if e == io.EOF { + break + } else if e != nil { + return nil, e + } + entry, err := parseListLine(line) + if err == nil { + entries = append(entries, entry) + } + } + return +} + +// ChangeDir issues a CWD FTP command, which changes the current directory to +// the specified path. +func (c *ServerConn) ChangeDir(path string) error { + _, _, err := c.cmd(StatusRequestedFileActionOK, "CWD %s", path) + return err +} + +// ChangeDirToParent issues a CDUP FTP command, which changes the current +// directory to the parent directory. This is similar to a call to ChangeDir +// with a path set to "..". +func (c *ServerConn) ChangeDirToParent() error { + _, _, err := c.cmd(StatusRequestedFileActionOK, "CDUP") + return err +} + +// CurrentDir issues a PWD FTP command, which Returns the path of the current +// directory. +func (c *ServerConn) CurrentDir() (string, error) { + _, msg, err := c.cmd(StatusPathCreated, "PWD") + if err != nil { + return "", err + } + + start := strings.Index(msg, "\"") + end := strings.LastIndex(msg, "\"") + + if start == -1 || end == -1 { + return "", errors.New("Unsuported PWD response format") + } + + return msg[start+1 : end], nil +} + +// Retr issues a RETR FTP command to fetch the specified file from the remote +// FTP server. +// +// The returned ReadCloser must be closed to cleanup the FTP data connection. +func (c *ServerConn) Retr(path string) (io.ReadCloser, error) { + conn, err := c.cmdDataConn("RETR %s", path) + if err != nil { + return nil, err + } + + r := &response{conn, c} + return r, nil +} + +// Stor issues a STOR FTP command to store a file to the remote FTP server. +// Stor creates the specified file with the content of the io.Reader. +// +// Hint: io.Pipe() can be used if an io.Writer is required. +func (c *ServerConn) Stor(path string, r io.Reader) error { + conn, err := c.cmdDataConn("STOR %s", path) + if err != nil { + return err + } + + _, err = io.Copy(conn, r) + conn.Close() + if err != nil { + return err + } + + _, _, err = c.conn.ReadResponse(StatusClosingDataConnection) + return err +} + +// Rename renames a file on the remote FTP server. +func (c *ServerConn) Rename(from, to string) error { + _, _, err := c.cmd(StatusRequestFilePending, "RNFR %s", from) + if err != nil { + return err + } + + _, _, err = c.cmd(StatusRequestedFileActionOK, "RNTO %s", to) + return err +} + +// Delete issues a DELE FTP command to delete the specified file from the +// remote FTP server. +func (c *ServerConn) Delete(path string) error { + _, _, err := c.cmd(StatusRequestedFileActionOK, "DELE %s", path) + return err +} + +// MakeDir issues a MKD FTP command to create the specified directory on the +// remote FTP server. +func (c *ServerConn) MakeDir(path string) error { + _, _, err := c.cmd(StatusPathCreated, "MKD %s", path) + return err +} + +// RemoveDir issues a RMD FTP command to remove the specified directory from +// the remote FTP server. +func (c *ServerConn) RemoveDir(path string) error { + _, _, err := c.cmd(StatusRequestedFileActionOK, "RMD %s", path) + return err +} + +// NoOp issues a NOOP FTP command. +// NOOP has no effects and is usually used to prevent the remote FTP server to +// close the otherwise idle connection. +func (c *ServerConn) NoOp() error { + _, _, err := c.cmd(StatusCommandOK, "NOOP") + return err +} + +// Logout issues a REIN FTP command to logout the current user. +func (c *ServerConn) Logout() error { + _, _, err := c.cmd(StatusLoggedIn, "REIN") + return err +} + +// Quit issues a QUIT FTP command to properly close the connection from the +// remote FTP server. +func (c *ServerConn) Quit() error { + c.conn.Cmd("QUIT") + return c.conn.Close() +} + +// Read implements the io.Reader interface on a FTP data connection. +func (r *response) Read(buf []byte) (int, error) { + n, err := r.conn.Read(buf) + return n, err +} + +// Close implements the io.Closer interface on a FTP data connection. +func (r *response) Close() error { + err := r.conn.Close() + _, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection) + if err2 != nil { + err = err2 + } + return err +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/parse_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/parse_test.go new file mode 100644 index 00000000..c76561b7 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/parse_test.go @@ -0,0 +1,63 @@ +package ftp + +import ( + "testing" + "time" +) + +var thisYear, _, _ = time.Now().Date() + +type line struct { + line string + name string + size uint64 + entryType EntryType + time time.Time +} + +var listTests = []line{ + // UNIX ls -l style + line{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 pub", "pub", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)}, + line{"drwxr-xr-x 3 110 1002 3 Dec 02 2009 p u b", "p u b", 0, EntryTypeFolder, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)}, + line{"-rwxr-xr-x 3 110 1002 1234567 Dec 02 2009 fileName", "fileName", 1234567, EntryTypeFile, time.Date(2009, time.December, 2, 0, 0, 0, 0, time.UTC)}, + line{"lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin", "bin -> usr/bin", 0, EntryTypeLink, time.Date(thisYear, time.January, 25, 0, 17, 0, 0, time.UTC)}, + // Microsoft's FTP servers for Windows + line{"---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z", "ls-lR.Z", 1803128, EntryTypeFile, time.Date(thisYear, time.July, 10, 10, 18, 0, 0, time.UTC)}, + line{"d--------- 1 owner group 0 May 9 19:45 Softlib", "Softlib", 0, EntryTypeFolder, time.Date(thisYear, time.May, 9, 19, 45, 0, 0, time.UTC)}, + // WFTPD for MSDOS + line{"-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp", "message.ftp", 322, EntryTypeFile, time.Date(1996, time.August, 19, 0, 0, 0, 0, time.UTC)}, +} + +// Not supported, at least we should properly return failure +var listTestsFail = []line{ + line{"d [R----F--] supervisor 512 Jan 16 18:53 login", "login", 0, EntryTypeFolder, time.Date(thisYear, time.January, 16, 18, 53, 0, 0, time.UTC)}, + line{"- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe", "cx.exe", 0, EntryTypeFile, time.Date(thisYear, time.October, 20, 15, 27, 0, 0, time.UTC)}, +} + +func TestParseListLine(t *testing.T) { + for _, lt := range listTests { + entry, err := parseListLine(lt.line) + if err != nil { + t.Errorf("parseListLine(%v) returned err = %v", lt.line, err) + continue + } + if entry.Name != lt.name { + t.Errorf("parseListLine(%v).Name = '%v', want '%v'", lt.line, entry.Name, lt.name) + } + if entry.Type != lt.entryType { + t.Errorf("parseListLine(%v).EntryType = %v, want %v", lt.line, entry.Type, lt.entryType) + } + if entry.Size != lt.size { + t.Errorf("parseListLine(%v).Size = %v, want %v", lt.line, entry.Size, lt.size) + } + if entry.Time.Unix() != lt.time.Unix() { + t.Errorf("parseListLine(%v).Time = %v, want %v", lt.line, entry.Time, lt.time) + } + } + for _, lt := range listTestsFail { + _, err := parseListLine(lt.line) + if err == nil { + t.Errorf("parseListLine(%v) expected to fail", lt.line) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/status.go b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/status.go new file mode 100644 index 00000000..50ee3765 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/jlaffaye/ftp/status.go @@ -0,0 +1,110 @@ +package ftp + +const ( + // Positive Preliminary reply + StatusInitiating = 100 + StatusRestartMarker = 110 + StatusReadyMinute = 120 + StatusAlreadyOpen = 125 + StatusAboutToSend = 150 + + // Positive Completion reply + StatusCommandOK = 200 + StatusCommandNotImplemented = 202 + StatusSystem = 211 + StatusDirectory = 212 + StatusFile = 213 + StatusHelp = 214 + StatusName = 215 + StatusReady = 220 + StatusClosing = 221 + StatusDataConnectionOpen = 225 + StatusClosingDataConnection = 226 + StatusPassiveMode = 227 + StatusLongPassiveMode = 228 + StatusExtendedPassiveMode = 229 + StatusLoggedIn = 230 + StatusLoggedOut = 231 + StatusLogoutAck = 232 + StatusRequestedFileActionOK = 250 + StatusPathCreated = 257 + + // Positive Intermediate reply + StatusUserOK = 331 + StatusLoginNeedAccount = 332 + StatusRequestFilePending = 350 + + // Transient Negative Completion reply + StatusNotAvailable = 421 + StatusCanNotOpenDataConnection = 425 + StatusTransfertAborted = 426 + StatusInvalidCredentials = 430 + StatusHostUnavailable = 434 + StatusFileActionIgnored = 450 + StatusActionAborted = 451 + Status452 = 452 + + // Permanent Negative Completion reply + StatusBadCommand = 500 + StatusBadArguments = 501 + StatusNotImplemented = 502 + StatusBadSequence = 503 + StatusNotImplementedParameter = 504 + StatusNotLoggedIn = 530 + StatusStorNeedAccount = 532 + StatusFileUnavailable = 550 + StatusPageTypeUnknown = 551 + StatusExceededStorage = 552 + StatusBadFileName = 553 +) + +var statusText = map[int]string{ + // 200 + StatusCommandOK: "Command okay.", + StatusCommandNotImplemented: "Command not implemented, superfluous at this site.", + StatusSystem: "System status, or system help reply.", + StatusDirectory: "Directory status.", + StatusFile: "File status.", + StatusHelp: "Help message.", + StatusName: "", + StatusReady: "Service ready for new user.", + StatusClosing: "Service closing control connection.", + StatusDataConnectionOpen: "Data connection open; no transfer in progress.", + StatusClosingDataConnection: "Closing data connection. Requested file action successful.", + StatusPassiveMode: "Entering Passive Mode.", + StatusLongPassiveMode: "Entering Long Passive Mode.", + StatusExtendedPassiveMode: "Entering Extended Passive Mode.", + StatusLoggedIn: "User logged in, proceed.", + StatusLoggedOut: "User logged out; service terminated.", + StatusLogoutAck: "Logout command noted, will complete when transfer done.", + StatusRequestedFileActionOK: "Requested file action okay, completed.", + StatusPathCreated: "Path created.", + + // 300 + StatusUserOK: "User name okay, need password.", + StatusLoginNeedAccount: "Need account for login.", + StatusRequestFilePending: "Requested file action pending further information.", + + // 400 + StatusNotAvailable: "Service not available, closing control connection.", + StatusCanNotOpenDataConnection: "Can't open data connection.", + StatusTransfertAborted: "Connection closed; transfer aborted.", + StatusInvalidCredentials: "Invalid username or password.", + StatusHostUnavailable: "Requested host unavailable.", + StatusFileActionIgnored: "Requested file action not taken.", + StatusActionAborted: "Requested action aborted. Local error in processing.", + Status452: "Insufficient storage space in system.", + + // 500 + StatusBadCommand: "Command unrecognized.", + StatusBadArguments: "Syntax error in parameters or arguments.", + StatusNotImplemented: "Command not implemented.", + StatusBadSequence: "Bad sequence of commands.", + StatusNotImplementedParameter: "Command not implemented for that parameter.", + StatusNotLoggedIn: "Not logged in.", + StatusStorNeedAccount: "Need account for storing files.", + StatusFileUnavailable: "File unavailable.", + StatusPageTypeUnknown: "Page type unknown.", + StatusExceededStorage: "Exceeded storage allocation.", + StatusBadFileName: "File name not allowed.", +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/.travis.yml b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/.travis.yml new file mode 100644 index 00000000..580d9b1f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - go get code.google.com/p/go.tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/README.md new file mode 100644 index 00000000..ab65d7dd --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/README.md @@ -0,0 +1,40 @@ +# go-shellwords + +[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master) +[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords) + +Parse line as shell words. + +## Usage + +```go +args, err := shellwords.Parse("./foo --bar=baz") +// args should be ["./foo", "--bar=baz"] +``` + +```go +os.Setenv("FOO", "bar") +p := shellwords.NewParser() +p.ParseEnv = true +args, err := p.Parse("./foo $FOO") +// args should be ["./foo", "bar"] +``` + +```go +p := shellwords.NewParser() +p.ParseBacktick = true +args, err := p.Parse("./foo `echo $SHELL`") +// args should be ["./foo", "/bin/bash"] +``` + +# Thanks + +This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). + +# License + +under the MIT License: http://mattn.mit-license.org/2014 + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords.go new file mode 100644 index 00000000..11ff3426 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords.go @@ -0,0 +1,129 @@ +package shellwords + +import ( + "errors" + "os" + "regexp" + "strings" +) + +var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`) + +func isSpace(r rune) bool { + switch r { + case ' ', '\t', '\r', '\n': + return true + } + return false +} + +func replaceEnv(s string) string { + return envRe.ReplaceAllStringFunc(s, func(s string) string { + s = s[1:] + if s[0] == '{' { + s = s[1 : len(s)-1] + } + return os.Getenv(s) + }) +} + +type Parser struct { + ParseEnv bool + ParseBacktick bool +} + +func NewParser() *Parser { + return new(Parser) +} + +func (p *Parser) Parse(line string) ([]string, error) { + line = strings.TrimSpace(line) + + args := []string{} + buf := "" + var escaped, doubleQuoted, singleQuoted, backQuote bool + backtick := "" + + for _, r := range line { + if escaped { + buf += string(r) + escaped = false + continue + } + + if r == '\\' { + if singleQuoted { + buf += string(r) + } else { + escaped = true + } + continue + } + + if isSpace(r) { + if singleQuoted || doubleQuoted || backQuote { + buf += string(r) + backtick += string(r) + } else if buf != "" { + if p.ParseEnv { + buf = replaceEnv(buf) + } + args = append(args, buf) + buf = "" + } + continue + } + + switch r { + case '`': + if !singleQuoted && !doubleQuoted { + if p.ParseBacktick { + if backQuote { + out, err := shellRun(backtick) + if err != nil { + return nil, err + } + buf = out + } + backtick = "" + backQuote = !backQuote + continue + } + backtick = "" + backQuote = !backQuote + } + case '"': + if !singleQuoted { + doubleQuoted = !doubleQuoted + continue + } + case '\'': + if !doubleQuoted { + singleQuoted = !singleQuoted + continue + } + } + + buf += string(r) + if backQuote { + backtick += string(r) + } + } + + if buf != "" { + if p.ParseEnv { + buf = replaceEnv(buf) + } + args = append(args, buf) + } + + if escaped || singleQuoted || doubleQuoted || backQuote { + return nil, errors.New("invalid command line string") + } + + return args, nil +} + +func Parse(line string) ([]string, error) { + return NewParser().Parse(line) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords_test.go new file mode 100644 index 00000000..fb96d33d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/shellwords_test.go @@ -0,0 +1,132 @@ +package shellwords + +import ( + "os" + "reflect" + "testing" +) + +var testcases = []struct { + line string + expected []string +}{ + {`var --bar=baz`, []string{`var`, `--bar=baz`}}, + {`var --bar="baz"`, []string{`var`, `--bar=baz`}}, + {`var "--bar=baz"`, []string{`var`, `--bar=baz`}}, + {`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}}, + {"var --bar=`baz`", []string{`var`, "--bar=`baz`"}}, + {`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}}, + {`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}}, + {`var --bar='\'`, []string{`var`, `--bar=\`}}, + {`var "--bar baz"`, []string{`var`, `--bar baz`}}, + {`var --"bar baz"`, []string{`var`, `--bar baz`}}, + {`var --"bar baz"`, []string{`var`, `--bar baz`}}, +} + +func TestSimple(t *testing.T) { + for _, testcase := range testcases { + args, err := Parse(testcase.line) + if err != nil { + t.Fatalf(err.Error()) + } + if !reflect.DeepEqual(args, testcase.expected) { + t.Fatalf("Expected %v, but %v:", testcase.expected, args) + } + } +} + +func TestError(t *testing.T) { + _, err := Parse("foo '") + if err == nil { + t.Fatalf("Should be an error") + } + _, err = Parse(`foo "`) + if err == nil { + t.Fatalf("Should be an error") + } + + _, err = Parse("foo `") + if err == nil { + t.Fatalf("Should be an error") + } +} + +func TestBacktick(t *testing.T) { + goversion, err := shellRun("go version") + if err != nil { + t.Fatalf(err.Error()) + } + + parser := NewParser() + parser.ParseBacktick = true + args, err := parser.Parse("echo `go version`") + if err != nil { + t.Fatalf(err.Error()) + } + expected := []string{"echo", goversion} + if !reflect.DeepEqual(args, expected) { + t.Fatalf("Expected %v, but %v:", expected, args) + } +} + +func TestBacktickError(t *testing.T) { + parser := NewParser() + parser.ParseBacktick = true + _, err := parser.Parse("echo `go Version`") + if err == nil { + t.Fatalf("Should be an error") + } +} + +func TestEnv(t *testing.T) { + os.Setenv("FOO", "bar") + + parser := NewParser() + parser.ParseEnv = true + args, err := parser.Parse("echo $FOO") + if err != nil { + t.Fatalf(err.Error()) + } + expected := []string{"echo", "bar"} + if !reflect.DeepEqual(args, expected) { + t.Fatalf("Expected %v, but %v:", expected, args) + } +} + +func TestNoEnv(t *testing.T) { + parser := NewParser() + parser.ParseEnv = true + args, err := parser.Parse("echo $BAR") + if err != nil { + t.Fatalf(err.Error()) + } + expected := []string{"echo", ""} + if !reflect.DeepEqual(args, expected) { + t.Fatalf("Expected %v, but %v:", expected, args) + } +} + +func TestDupEnv(t *testing.T) { + os.Setenv("FOO", "bar") + os.Setenv("FOO_BAR", "baz") + + parser := NewParser() + parser.ParseEnv = true + args, err := parser.Parse("echo $$FOO$") + if err != nil { + t.Fatalf(err.Error()) + } + expected := []string{"echo", "$bar$"} + if !reflect.DeepEqual(args, expected) { + t.Fatalf("Expected %v, but %v:", expected, args) + } + + args, err = parser.Parse("echo $${FOO_BAR}$") + if err != nil { + t.Fatalf(err.Error()) + } + expected = []string{"echo", "$baz$"} + if !reflect.DeepEqual(args, expected) { + t.Fatalf("Expected %v, but %v:", expected, args) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_posix.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_posix.go new file mode 100644 index 00000000..4f8ac55e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_posix.go @@ -0,0 +1,19 @@ +// +build !windows + +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line string) (string, error) { + shell := os.Getenv("SHELL") + b, err := exec.Command(shell, "-c", line).Output() + if err != nil { + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_windows.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_windows.go new file mode 100644 index 00000000..7cad4cf0 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-shellwords/util_windows.go @@ -0,0 +1,17 @@ +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line string) (string, error) { + shell := os.Getenv("COMSPEC") + b, err := exec.Command(shell, "/c", line).Output() + if err != nil { + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/README.md index 70e7f03f..c2ac2d42 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/README.md +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/README.md @@ -1,4 +1,41 @@ -# goamz - An Amazon Library for Go +# goamz - An Amazon Library for Go + +Current API documentation: [![GoDoc](https://godoc.org/github.com/mitchellh/goamz?status.svg)](https://godoc.org/github.com/mitchellh/goamz) This is a fork of [https://launchpad.net/goamz](https://launchpad.net/goamz) that adds some missing API calls to certain packages. + +This library is *incomplete*, but implements a large amount of the AWS API. +It is heavily used in projects such as +[Terraform](https://github.com/hashicorp/terraform) and +[Packer](https://github.com/mitchellh/packer). +If you find anything missing from this library, +please [file an issue](https://github.com/mitchellh/goamz). + +## Example Usage + +```go +package main + +import ( + "github.com/mitchellh/goamz/aws" + "github.com/mitchellh/goamz/s3" + "log" + "fmt" +) + +func main() { + auth, err := aws.EnvAuth() + if err != nil { + log.Fatal(err) + } + client := s3.New(auth, aws.USEast) + resp, err := client.ListBuckets() + + if err != nil { + log.Fatal(err) + } + + log.Print(fmt.Sprintf("%T %+v", resp.Buckets[0], resp.Buckets[0])) +} +``` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling.go index 5c581c3d..e2cef98e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling.go @@ -4,12 +4,13 @@ package autoscaling import ( "encoding/xml" - "github.com/mitchellh/goamz/aws" "net/http" "net/url" "strconv" "strings" "time" + + "github.com/mitchellh/goamz/aws" ) // The AutoScaling type encapsulates operations operations with the autoscaling endpoint. @@ -109,11 +110,13 @@ type LoadBalancerName struct { } type LaunchConfiguration struct { - ImageId string `xml:"member>ImageId"` - InstanceType string `xml:"member>InstanceType"` - KeyName string `xml:"member>KeyName"` - Name string `xml:"member>LaunchConfigurationName"` - SecurityGroups []SecurityGroup `xml:"member>SecurityGroups"` + IamInstanceProfile string `xml:"member>IamInstanceProfile"` + ImageId string `xml:"member>ImageId"` + InstanceType string `xml:"member>InstanceType"` + KeyName string `xml:"member>KeyName"` + Name string `xml:"member>LaunchConfigurationName"` + SecurityGroups []SecurityGroup `xml:"member>SecurityGroups"` + UserData []byte `xml:"member>UserData"` } type AutoScalingGroup struct { @@ -147,6 +150,7 @@ type CreateAutoScalingGroup struct { MaxSize int MinSize int PlacementGroup string + TerminationPolicies []string Name string Tags []Tag VPCZoneIdentifier []string @@ -212,6 +216,10 @@ func (autoscaling *AutoScaling) CreateAutoScalingGroup(options *CreateAutoScalin params["Tag.member."+strconv.Itoa(j+1)+".Value"] = tag.Value } + for i, v := range options.TerminationPolicies { + params["TerminationPolicies.member."+strconv.Itoa(i+1)] = v + } + if options.VPCZoneIdentifier != nil { params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",") } @@ -235,6 +243,7 @@ type CreateLaunchConfiguration struct { KeyName string Name string SecurityGroups []string + UserData string } func (autoscaling *AutoScaling) CreateLaunchConfiguration(options *CreateLaunchConfiguration) (resp *SimpleResp, err error) { @@ -260,6 +269,12 @@ func (autoscaling *AutoScaling) CreateLaunchConfiguration(options *CreateLaunchC params["SecurityGroups.member."+strconv.Itoa(i+1)] = v } + if options.UserData != "" { + userData := make([]byte, b64.EncodedLen(len(options.UserData))) + b64.Encode(userData, []byte(options.UserData)) + params["UserData"] = string(userData) + } + resp = &SimpleResp{} err = autoscaling.query(params, resp) @@ -389,6 +404,7 @@ type UpdateAutoScalingGroup struct { MaxSize int MinSize int PlacementGroup string + TerminationPolicies []string Name string VPCZoneIdentifier []string @@ -441,6 +457,9 @@ func (autoscaling *AutoScaling) UpdateAutoScalingGroup(options *UpdateAutoScalin if options.PlacementGroup != "" { params["PlacementGroup"] = options.PlacementGroup } + for i, v := range options.TerminationPolicies { + params["TerminationPolicies.member."+strconv.Itoa(i+1)] = v + } if options.VPCZoneIdentifier != nil { params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",") diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling_test.go index 74619701..8cccdc2b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/autoscaling/autoscaling_test.go @@ -1,11 +1,12 @@ package autoscaling_test import ( + "testing" + "github.com/mitchellh/goamz/autoscaling" "github.com/mitchellh/goamz/aws" "github.com/mitchellh/goamz/testutil" . "github.com/motain/gocheck" - "testing" ) func Test(t *testing.T) { @@ -44,6 +45,7 @@ func (s *S) Test_CreateAutoScalingGroup(c *C) { MinSize: 2, MaxSize: 2, PlacementGroup: "foobar", + TerminationPolicies: []string{"ClosestToNextInstanceHour", "OldestInstance"}, Name: "foobar", Tags: []autoscaling.Tag{ autoscaling.Tag{ @@ -60,6 +62,8 @@ func (s *S) Test_CreateAutoScalingGroup(c *C) { c.Assert(req.Form["Action"], DeepEquals, []string{"CreateAutoScalingGroup"}) c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-foo"}) c.Assert(req.Form["VPCZoneIdentifier"], DeepEquals, []string{"foo,bar"}) + c.Assert(req.Form["TerminationPolicies.member.1"], DeepEquals, []string{"ClosestToNextInstanceHour"}) + c.Assert(req.Form["TerminationPolicies.member.2"], DeepEquals, []string{"OldestInstance"}) c.Assert(err, IsNil) c.Assert(resp.RequestId, Equals, "8d798a29-f083-11e1-bdfb-cb223EXAMPLE") } @@ -74,6 +78,7 @@ func (s *S) Test_CreateLaunchConfiguration(c *C) { InstanceType: "m1.small", KeyName: "foobar", Name: "i-141421", + UserData: "#!/bin/bash\necho Hello\n", } resp, err := s.autoscaling.CreateLaunchConfiguration(&options) @@ -82,6 +87,7 @@ func (s *S) Test_CreateLaunchConfiguration(c *C) { c.Assert(req.Form["Action"], DeepEquals, []string{"CreateLaunchConfiguration"}) c.Assert(req.Form["InstanceType"], DeepEquals, []string{"m1.small"}) c.Assert(req.Form["SecurityGroups.member.1"], DeepEquals, []string{"sg-1111"}) + c.Assert(req.Form["UserData"], DeepEquals, []string{"IyEvYmluL2Jhc2gKZWNobyBIZWxsbwo="}) c.Assert(err, IsNil) c.Assert(resp.RequestId, Equals, "7c6e177f-f082-11e1-ac58-3714bEXAMPLE") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws.go index 7ef367bd..85e395e6 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws.go @@ -15,6 +15,8 @@ import ( "fmt" "io/ioutil" "os" + + "github.com/vaughan0/go-ini" ) // Region defines the URLs where AWS services may be accessed. @@ -190,6 +192,23 @@ var SAEast = Region{ "https://route53.amazonaws.com", } +var CNNorth = Region{ + "cn-north-1", + "https://ec2.cn-north-1.amazonaws.com.cn", + "https://s3.cn-north-1.amazonaws.com.cn", + "", + true, + true, + "", + "https://sns.cn-north-1.amazonaws.com.cn", + "https://sqs.cn-north-1.amazonaws.com.cn", + "https://iam.cn-north-1.amazonaws.com.cn", + "https://elasticloadbalancing.cn-north-1.amazonaws.com.cn", + "https://autoscaling.cn-north-1.amazonaws.com.cn", + "https://rds.cn-north-1.amazonaws.com.cn", + "https://route53.amazonaws.com", +} + var Regions = map[string]Region{ APNortheast.Name: APNortheast, APSoutheast.Name: APSoutheast, @@ -200,6 +219,7 @@ var Regions = map[string]Region{ USWest2.Name: USWest2, SAEast.Name: SAEast, USGovWest.Name: USGovWest, + CNNorth.Name: CNNorth, } type Auth struct { @@ -278,6 +298,13 @@ func GetAuth(accessKey string, secretKey string) (auth Auth, err error) { return Auth{accessKey, secretKey, ""}, nil } + // Next try to get auth from the environment + auth, err = SharedAuth() + if err == nil { + // Found auth, return + return + } + // Next try to get auth from the environment auth, err = EnvAuth() if err == nil { @@ -298,8 +325,53 @@ func GetAuth(accessKey string, secretKey string) (auth Auth, err error) { return } +// SharedAuth creates an Auth based on shared credentials stored in +// $HOME/.aws/credentials. The AWS_PROFILE environment variables is used to +// select the profile. +func SharedAuth() (auth Auth, err error) { + var profileName = os.Getenv("AWS_PROFILE") + + if profileName == "" { + profileName = "default" + } + + var credentialsFile = os.Getenv("AWS_CREDENTIAL_FILE") + if credentialsFile == "" { + var homeDir = os.Getenv("HOME") + if homeDir == "" { + err = errors.New("Could not get HOME") + return + } + credentialsFile = homeDir + "/.aws/credentials" + } + + file, err := ini.LoadFile(credentialsFile) + if err != nil { + err = errors.New("Couldn't parse AWS credentials file") + return + } + + var profile = file[profileName] + if profile == nil { + err = errors.New("Couldn't find profile in AWS credentials file") + return + } + + auth.AccessKey = profile["aws_access_key_id"] + auth.SecretKey = profile["aws_secret_access_key"] + + if auth.AccessKey == "" { + err = errors.New("AWS_ACCESS_KEY_ID not found in environment in credentials file") + } + if auth.SecretKey == "" { + err = errors.New("AWS_SECRET_ACCESS_KEY not found in credentials file") + } + return +} + // EnvAuth creates an Auth based on environment information. // The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment +// For accounts that require a security token, it is read from AWS_SECURITY_TOKEN // variables are used. func EnvAuth() (auth Auth, err error) { auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID") @@ -311,6 +383,9 @@ func EnvAuth() (auth Auth, err error) { if auth.SecretKey == "" { auth.SecretKey = os.Getenv("AWS_SECRET_KEY") } + + auth.Token = os.Getenv("AWS_SECURITY_TOKEN") + if auth.AccessKey == "" { err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws_test.go index 54ef62ac..78cbbaf0 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/aws/aws_test.go @@ -3,6 +3,7 @@ package aws_test import ( "github.com/mitchellh/goamz/aws" . "github.com/motain/gocheck" + "io/ioutil" "os" "strings" "testing" @@ -30,6 +31,110 @@ func (s *S) TearDownTest(c *C) { } } +func (s *S) TestSharedAuthNoHome(c *C) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "foo") + _, err := aws.SharedAuth() + c.Assert(err, ErrorMatches, "Could not get HOME") +} + +func (s *S) TestSharedAuthNoCredentialsFile(c *C) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "foo") + os.Setenv("HOME", "/tmp") + _, err := aws.SharedAuth() + c.Assert(err, ErrorMatches, "Couldn't parse AWS credentials file") +} + +func (s *S) TestSharedAuthNoProfileInFile(c *C) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "foo") + + d, err := ioutil.TempDir("", "") + if err != nil { + panic(err) + } + defer os.RemoveAll(d) + + err = os.Mkdir(d+"/.aws", 0755) + if err != nil { + panic(err) + } + + ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\n"), 0644) + os.Setenv("HOME", d) + + _, err = aws.SharedAuth() + c.Assert(err, ErrorMatches, "Couldn't find profile in AWS credentials file") +} + +func (s *S) TestSharedAuthNoKeysInProfile(c *C) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "bar") + + d, err := ioutil.TempDir("", "") + if err != nil { + panic(err) + } + defer os.RemoveAll(d) + + err = os.Mkdir(d+"/.aws", 0755) + if err != nil { + panic(err) + } + + ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\nawsaccesskeyid = AK.."), 0644) + os.Setenv("HOME", d) + + _, err = aws.SharedAuth() + c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY not found in credentials file") +} + +func (s *S) TestSharedAuthDefaultCredentials(c *C) { + os.Clearenv() + + d, err := ioutil.TempDir("", "") + if err != nil { + panic(err) + } + defer os.RemoveAll(d) + + err = os.Mkdir(d+"/.aws", 0755) + if err != nil { + panic(err) + } + + ioutil.WriteFile(d+"/.aws/credentials", []byte("[default]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644) + os.Setenv("HOME", d) + + auth, err := aws.SharedAuth() + c.Assert(err, IsNil) + c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) +} + +func (s *S) TestSharedAuth(c *C) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "bar") + + d, err := ioutil.TempDir("", "") + if err != nil { + panic(err) + } + defer os.RemoveAll(d) + + err = os.Mkdir(d+"/.aws", 0755) + if err != nil { + panic(err) + } + + ioutil.WriteFile(d+"/.aws/credentials", []byte("[bar]\naws_access_key_id = access\naws_secret_access_key = secret\n"), 0644) + os.Setenv("HOME", d) + + auth, err := aws.SharedAuth() + c.Assert(err, IsNil) + c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) +} + func (s *S) TestEnvAuthNoSecret(c *C) { os.Clearenv() _, err := aws.EnvAuth() @@ -52,6 +157,16 @@ func (s *S) TestEnvAuth(c *C) { c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"}) } +func (s *S) TestEnvAuthWithToken(c *C) { + os.Clearenv() + os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + os.Setenv("AWS_ACCESS_KEY_ID", "access") + os.Setenv("AWS_SECURITY_TOKEN", "token") + auth, err := aws.EnvAuth() + c.Assert(err, IsNil) + c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access", Token: "token"}) +} + func (s *S) TestEnvAuthAlt(c *C) { os.Clearenv() os.Setenv("AWS_SECRET_KEY", "secret") diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2.go index 6042aa61..0d9b09be 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2.go @@ -178,7 +178,7 @@ func buildError(r *http.Response) error { err.RequestId = errors.RequestId err.StatusCode = r.StatusCode if err.Message == "" { - err.Message = r.Status + err.Message = err.Code } return &err } @@ -220,6 +220,8 @@ func addBlockDeviceParams(prename string, params map[string]string, blockdevices } if k.DeleteOnTermination { params[prefix+"Ebs.DeleteOnTermination"] = "true" + } else { + params[prefix+"Ebs.DeleteOnTermination"] = "false" } if k.Encrypted { params[prefix+"Ebs.Encrypted"] = "true" @@ -253,6 +255,7 @@ type RunInstances struct { SubnetId string AssociatePublicIpAddress bool DisableAPITermination bool + EbsOptimized bool ShutdownBehavior string PrivateIPAddress string BlockDevices []BlockDeviceMapping @@ -269,6 +272,15 @@ type RunInstancesResp struct { Instances []Instance `xml:"instancesSet>item"` } +// BlockDevice represents the association of a block device with an instance. +type BlockDevice struct { + DeviceName string `xml:"deviceName"` + VolumeId string `xml:"ebs>volumeId"` + Status string `xml:"ebs>status"` + AttachTime string `xml:"ebs>attachTime"` + DeleteOnTermination bool `xml:"ebs>deleteOnTermination"` +} + // Instance encapsulates a running instance in EC2. // // See http://goo.gl/OCH8a for more details. @@ -296,6 +308,8 @@ type Instance struct { LaunchTime time.Time `xml:"launchTime"` SourceDestCheck bool `xml:"sourceDestCheck"` SecurityGroups []SecurityGroup `xml:"groupSet>item"` + EbsOptimized string `xml:"ebsOptimized"` + BlockDevices []BlockDevice `xml:"blockDeviceMapping>item"` } // RunInstances starts new instances in EC2. @@ -392,6 +406,9 @@ func (ec2 *EC2) RunInstances(options *RunInstances) (resp *RunInstancesResp, err if options.DisableAPITermination { params["DisableApiTermination"] = "true" } + if options.EbsOptimized { + params["EbsOptimized"] = "true" + } if options.ShutdownBehavior != "" { params["InstanceInitiatedShutdownBehavior"] = options.ShutdownBehavior } @@ -419,6 +436,78 @@ func clientToken() (string, error) { return hex.EncodeToString(buf), nil } +// ---------------------------------------------------------------------------- +// Instance events and status functions and types. + +// The DescribeInstanceStatus type encapsulates options for the respective request in EC2. +// +// See http://goo.gl/DFySJY for more details. +type EventsSet struct { + Code string `xml:"code"` + Description string `xml:"description"` + NotBefore string `xml:"notBefore"` + NotAfter string `xml:"notAfter"` +} + +type StatusDetails struct { + Name string `xml:"name"` + Status string `xml:"status"` + ImpairedSince string `xml:"impairedSince"` +} + +type Status struct { + Status string `xml:"status"` + Details []StatusDetails `xml:"details>item"` +} + +type InstanceStatusSet struct { + InstanceId string `xml:"instanceId"` + AvailabilityZone string `xml:"availabilityZone"` + InstanceState InstanceState `xml:"instanceState"` + SystemStatus Status `xml:"systemStatus"` + InstanceStatus Status `xml:"instanceStatus"` + Events []EventsSet `xml:"eventsSet>item"` +} + +type DescribeInstanceStatusResp struct { + RequestId string `xml:"requestId"` + InstanceStatus []InstanceStatusSet `xml:"instanceStatusSet>item"` +} + +type DescribeInstanceStatus struct { + InstanceIds []string + IncludeAllInstances bool + MaxResults int64 + NextToken string +} + +func (ec2 *EC2) DescribeInstanceStatus(options *DescribeInstanceStatus, filter *Filter) (resp *DescribeInstanceStatusResp, err error) { + params := makeParams("DescribeInstanceStatus") + if options.IncludeAllInstances { + params["IncludeAllInstances"] = "true" + } + if len(options.InstanceIds) > 0 { + addParamsList(params, "InstanceIds", options.InstanceIds) + } + if options.MaxResults > 0 { + params["MaxResults"] = strconv.FormatInt(options.MaxResults, 10) + } + if options.NextToken != "" { + params["NextToken"] = options.NextToken + } + if filter != nil { + filter.addParams(params) + } + + resp = &DescribeInstanceStatusResp{} + err = ec2.query(params, resp) + if err != nil { + return nil, err + } + + return +} + // ---------------------------------------------------------------------------- // Spot Instance management functions and types. @@ -478,6 +567,12 @@ type SpotLaunchSpec struct { BlockDevices []BlockDeviceMapping `xml:"blockDeviceMapping>item"` } +type SpotStatus struct { + Code string `xml:"code"` + UpdateTime string `xml:"updateTime"` + Message string `xml:"message"` +} + type SpotRequestResult struct { SpotRequestId string `xml:"spotInstanceRequestId"` SpotPrice string `xml:"spotPrice"` @@ -485,6 +580,7 @@ type SpotRequestResult struct { AvailZone string `xml:"launchedAvailabilityZone"` InstanceId string `xml:"instanceId"` State string `xml:"state"` + Status SpotStatus `xml:"status"` SpotLaunchSpec SpotLaunchSpec `xml:"launchSpecification"` CreateTime string `xml:"createTime"` Tags []Tag `xml:"tagSet>item"` @@ -636,6 +732,61 @@ func (ec2 *EC2) CancelSpotRequests(spotrequestIds []string) (resp *CancelSpotReq return } +type DescribeSpotPriceHistory struct { + InstanceType []string + ProductDescription []string + AvailabilityZone string + StartTime, EndTime time.Time +} + +// Response to a DescribeSpotPriceHisotyr request. +// +// See http://goo.gl/3BKHj for more details. +type DescribeSpotPriceHistoryResp struct { + RequestId string `xml:"requestId"` + History []SpotPriceHistory `xml:"spotPriceHistorySet>item"` +} + +type SpotPriceHistory struct { + InstanceType string `xml:"instanceType"` + ProductDescription string `xml:"productDescription"` + SpotPrice string `xml:"spotPrice"` + Timestamp time.Time `xml:"timestamp"` + AvailabilityZone string `xml:"availabilityZone"` +} + +// DescribeSpotPriceHistory gets the spot pricing history. +// +// See http://goo.gl/3BKHj for more details. +func (ec2 *EC2) DescribeSpotPriceHistory(o *DescribeSpotPriceHistory) (resp *DescribeSpotPriceHistoryResp, err error) { + params := makeParams("DescribeSpotPriceHistory") + if o.AvailabilityZone != "" { + params["AvailabilityZone"] = o.AvailabilityZone + } + + if !o.StartTime.IsZero() { + params["StartTime"] = o.StartTime.In(time.UTC).Format(time.RFC3339) + } + if !o.EndTime.IsZero() { + params["EndTime"] = o.EndTime.In(time.UTC).Format(time.RFC3339) + } + + if len(o.InstanceType) > 0 { + addParamsList(params, "InstanceType", o.InstanceType) + } + if len(o.ProductDescription) > 0 { + addParamsList(params, "ProductDescription", o.ProductDescription) + } + + resp = &DescribeSpotPriceHistoryResp{} + err = ec2.query(params, resp) + if err != nil { + return nil, err + } + + return +} + // Response to a TerminateInstances request. // // See http://goo.gl/3BKHj for more details. @@ -1834,6 +1985,29 @@ func (ec2 *EC2) CreateTags(resourceIds []string, tags []Tag) (resp *SimpleResp, return resp, nil } +type TagsResp struct { + RequestId string `xml:"requestId"` + Tags []ResourceTag `xml:"tagSet>item"` +} + +type ResourceTag struct { + Tag + ResourceId string `xml:"resourceId"` + ResourceType string `xml:"resourceType"` +} + +func (ec2 *EC2) Tags(filter *Filter) (*TagsResp, error) { + params := makeParams("DescribeTags") + filter.addParams(params) + + resp := &TagsResp{} + if err := ec2.query(params, resp); err != nil { + return nil, err + } + + return resp, nil +} + // Response to a StartInstances request. // // See http://goo.gl/awKeF for more details. @@ -2006,6 +2180,27 @@ type CreateVpcResp struct { VPC VPC `xml:"vpc"` } +// The ModifyVpcAttribute request parameters. +// +// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details. +type ModifyVpcAttribute struct { + EnableDnsSupport bool + EnableDnsHostnames bool + + SetEnableDnsSupport bool + SetEnableDnsHostnames bool +} + +// Response to a DescribeVpcAttribute request. +// +// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details. +type VpcAttributeResp struct { + RequestId string `xml:"requestId"` + VpcId string `xml:"vpcId"` + EnableDnsSupport bool `xml:"enableDnsSupport>value"` + EnableDnsHostnames bool `xml:"enableDnsHostnames>value"` +} + // CreateInternetGateway request parameters. // // http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateInternetGateway.html @@ -2074,6 +2269,19 @@ type CreateSubnetResp struct { Subnet Subnet `xml:"subnet"` } +// The ModifySubnetAttribute request parameters +// +// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html +type ModifySubnetAttribute struct { + SubnetId string + MapPublicIpOnLaunch bool +} + +type ModifySubnetAttributeResp struct { + RequestId string `xml:"requestId"` + Return bool `xml:"return"` +} + // Response to a DescribeInternetGateways request. type InternetGatewaysResp struct { RequestId string `xml:"requestId"` @@ -2208,6 +2416,49 @@ func (ec2 *EC2) DescribeVpcs(ids []string, filter *Filter) (resp *VpcsResp, err return } +// VpcAttribute describes an attribute of a VPC. +// You can specify only one attribute at a time. +// Valid attributes are: +// enableDnsSupport | enableDnsHostnames +// +// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeVpcAttribute.html for more details. +func (ec2 *EC2) VpcAttribute(vpcId, attribute string) (resp *VpcAttributeResp, err error) { + params := makeParams("DescribeVpcAttribute") + params["VpcId"] = vpcId + params["Attribute"] = attribute + + resp = &VpcAttributeResp{} + err = ec2.query(params, resp) + if err != nil { + return nil, err + } + return +} + +// ModifyVpcAttribute modifies the specified attribute of the specified VPC. +// +// See http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-ModifyVpcAttribute.html for more details. +func (ec2 *EC2) ModifyVpcAttribute(vpcId string, options *ModifyVpcAttribute) (*SimpleResp, error) { + params := makeParams("ModifyVpcAttribute") + + params["VpcId"] = vpcId + + if options.SetEnableDnsSupport { + params["EnableDnsSupport.Value"] = strconv.FormatBool(options.EnableDnsSupport) + } + + if options.SetEnableDnsHostnames { + params["EnableDnsHostnames.Value"] = strconv.FormatBool(options.EnableDnsHostnames) + } + + resp := &SimpleResp{} + if err := ec2.query(params, resp); err != nil { + return nil, err + } + + return resp, nil +} + // Create a new subnet. func (ec2 *EC2) CreateSubnet(options *CreateSubnet) (resp *CreateSubnetResp, err error) { params := makeParams("CreateSubnet") @@ -2237,6 +2488,26 @@ func (ec2 *EC2) DeleteSubnet(id string) (resp *SimpleResp, err error) { return } +// ModifySubnetAttribute +// +// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-ModifySubnetAttribute.html +func (ec2 *EC2) ModifySubnetAttribute(options *ModifySubnetAttribute) (resp *ModifySubnetAttributeResp, err error) { + params := makeParams("ModifySubnetAttribute") + params["SubnetId"] = options.SubnetId + if options.MapPublicIpOnLaunch { + params["MapPublicIpOnLaunch.Value"] = "true" + } else { + params["MapPublicIpOnLaunch.Value"] = "false" + } + + resp = &ModifySubnetAttributeResp{} + err = ec2.query(params, resp) + if err != nil { + return nil, err + } + return +} + // DescribeSubnets // // http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSubnets.html diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2_test.go index 4d0c08a3..93f2eaf3 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/ec2_test.go @@ -95,13 +95,13 @@ func (s *S) TestRunInstancesErrorWithoutXML(c *C) { testServer.WaitRequest() c.Assert(resp, IsNil) - c.Assert(err, ErrorMatches, "500 Internal Server Error") + c.Assert(err, ErrorMatches, "") ec2err, ok := err.(*ec2.Error) c.Assert(ok, Equals, true) c.Assert(ec2err.StatusCode, Equals, 500) c.Assert(ec2err.Code, Equals, "") - c.Assert(ec2err.Message, Equals, "500 Internal Server Error") + c.Assert(ec2err.Message, Equals, "") c.Assert(ec2err.RequestId, Equals, "") } @@ -114,13 +114,13 @@ func (s *S) TestRequestSpotInstancesErrorWithoutXML(c *C) { testServer.WaitRequest() c.Assert(resp, IsNil) - c.Assert(err, ErrorMatches, "500 Internal Server Error") + c.Assert(err, ErrorMatches, "") ec2err, ok := err.(*ec2.Error) c.Assert(ok, Equals, true) c.Assert(ec2err.StatusCode, Equals, 500) c.Assert(ec2err.Code, Equals, "") - c.Assert(ec2err.Message, Equals, "500 Internal Server Error") + c.Assert(ec2err.Message, Equals, "") c.Assert(ec2err.RequestId, Equals, "") } @@ -140,6 +140,7 @@ func (s *S) TestRunInstancesExample(c *C) { Monitoring: true, SubnetId: "subnet-id", DisableAPITermination: true, + EbsOptimized: true, ShutdownBehavior: "terminate", PrivateIPAddress: "10.0.0.25", BlockDevices: []ec2.BlockDeviceMapping{ @@ -168,6 +169,7 @@ func (s *S) TestRunInstancesExample(c *C) { c.Assert(req.Form["Monitoring.Enabled"], DeepEquals, []string{"true"}) c.Assert(req.Form["SubnetId"], DeepEquals, []string{"subnet-id"}) c.Assert(req.Form["DisableApiTermination"], DeepEquals, []string{"true"}) + c.Assert(req.Form["EbsOptimized"], DeepEquals, []string{"true"}) c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], DeepEquals, []string{"terminate"}) c.Assert(req.Form["PrivateIpAddress"], DeepEquals, []string{"10.0.0.25"}) c.Assert(req.Form["BlockDeviceMapping.1.DeviceName"], DeepEquals, []string{"/dev/sdb"}) @@ -266,6 +268,9 @@ func (s *S) TestRequestSpotInstancesExample(c *C) { c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5") c.Assert(resp.SpotRequestResults[0].State, Equals, "open") c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d") + c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "pending-evaluation") + c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z") + c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request has been submitted for review, and is pending evaluation.") } func (s *S) TestCancelSpotRequestsExample(c *C) { @@ -301,6 +306,7 @@ func (s *S) TestTerminateInstancesExample(c *C) { c.Assert(req.Form["Monitoring.Enabled"], IsNil) c.Assert(req.Form["SubnetId"], IsNil) c.Assert(req.Form["DisableApiTermination"], IsNil) + c.Assert(req.Form["EbsOptimized"], IsNil) c.Assert(req.Form["InstanceInitiatedShutdownBehavior"], IsNil) c.Assert(req.Form["PrivateIpAddress"], IsNil) @@ -334,6 +340,9 @@ func (s *S) TestDescribeSpotRequestsExample(c *C) { c.Assert(resp.SpotRequestResults[0].State, Equals, "active") c.Assert(resp.SpotRequestResults[0].SpotPrice, Equals, "0.5") c.Assert(resp.SpotRequestResults[0].SpotLaunchSpec.ImageId, Equals, "ami-1a2b3c4d") + c.Assert(resp.SpotRequestResults[0].Status.Code, Equals, "fulfilled") + c.Assert(resp.SpotRequestResults[0].Status.UpdateTime, Equals, "2008-05-07T12:51:50.000Z") + c.Assert(resp.SpotRequestResults[0].Status.Message, Equals, "Your Spot request is fulfilled.") } func (s *S) TestDescribeInstancesExample1(c *C) { @@ -366,6 +375,13 @@ func (s *S) TestDescribeInstancesExample1(c *C) { c.Assert(r0i.PrivateDNSName, Equals, "domU-12-31-39-10-56-34.compute-1.internal") c.Assert(r0i.DNSName, Equals, "ec2-174-129-165-232.compute-1.amazonaws.com") c.Assert(r0i.AvailZone, Equals, "us-east-1b") + + b0 := r0i.BlockDevices[0] + c.Assert(b0.DeviceName, Equals, "/dev/sda1") + c.Assert(b0.VolumeId, Equals, "vol-a082c1c9") + c.Assert(b0.Status, Equals, "attached") + c.Assert(b0.AttachTime, Equals, "2010-08-17T01:15:21.000Z") + c.Assert(b0.DeleteOnTermination, Equals, false) } func (s *S) TestDescribeInstancesExample2(c *C) { @@ -1041,6 +1057,31 @@ func (s *S) TestSignatureWithEndpointPath(c *C) { c.Assert(req.Form["Signature"], DeepEquals, []string{"QmvgkYGn19WirCuCz/jRp3RmRgFwWR5WRkKZ5AZnyXQ="}) } +func (s *S) TestDescribeInstanceStatusExample(c *C) { + testServer.Response(200, nil, DescribeInstanceStatusExample) + options := &ec2.DescribeInstanceStatus{} + resp, err := s.ec2.DescribeInstanceStatus(options, nil) + + req := testServer.WaitRequest() + c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstanceStatus"}) + + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE") + c.Assert(resp.InstanceStatus[0].InstanceId, Equals, "i-1a2b3c4d") + c.Assert(resp.InstanceStatus[0].InstanceState.Code, Equals, 16) + c.Assert(resp.InstanceStatus[0].SystemStatus.Status, Equals, "impaired") + c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Name, Equals, "reachability") + c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].Status, Equals, "failed") + c.Assert(resp.InstanceStatus[0].SystemStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z") + c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Name, Equals, "reachability") + c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].Status, Equals, "failed") + c.Assert(resp.InstanceStatus[0].InstanceStatus.Details[0].ImpairedSince, Equals, "YYYY-MM-DDTHH:MM:SS.000Z") + c.Assert(resp.InstanceStatus[0].Events[0].Code, Equals, "instance-retirement") + c.Assert(resp.InstanceStatus[0].Events[0].Description, Equals, "The instance is running on degraded hardware") + c.Assert(resp.InstanceStatus[0].Events[0].NotBefore, Equals, "YYYY-MM-DDTHH:MM:SS+0000") + c.Assert(resp.InstanceStatus[0].Events[0].NotAfter, Equals, "YYYY-MM-DDTHH:MM:SS+0000") +} + func (s *S) TestAllocateAddressExample(c *C) { testServer.Response(200, nil, AllocateAddressExample) @@ -1223,6 +1264,24 @@ func (s *S) TestCreateSubnet(c *C) { c.Assert(resp.Subnet.AvailableIpAddressCount, Equals, 251) } +func (s *S) TestModifySubnetAttribute(c *C) { + testServer.Response(200, nil, ModifySubnetAttributeExample) + + options := &ec2.ModifySubnetAttribute{ + SubnetId: "foo", + MapPublicIpOnLaunch: true, + } + + resp, err := s.ec2.ModifySubnetAttribute(options) + + req := testServer.WaitRequest() + c.Assert(req.Form["SubnetId"], DeepEquals, []string{"foo"}) + c.Assert(req.Form["MapPublicIpOnLaunch.Value"], DeepEquals, []string{"true"}) + + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "59dbff89-35bd-4eac-99ed-be587EXAMPLE") +} + func (s *S) TestResetImageAttribute(c *C) { testServer.Response(200, nil, ResetImageAttributeExample) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/responses_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/responses_test.go index 61f7a419..3666f70d 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/responses_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/ec2/responses_test.go @@ -108,7 +108,7 @@ var RequestSpotInstancesExample = ` open pending-evaluation - YYYY-MM-DDTHH:MM:SS.000Z + 2008-05-07T12:51:50.000Z Your Spot request has been submitted for review, and is pending evaluation. MyAzGroup @@ -147,7 +147,7 @@ var DescribeSpotRequestsExample = ` active fulfilled - YYYY-MM-DDTHH:MM:SS.000Z + 2008-05-07T12:51:50.000Z Your Spot request is fulfilled. @@ -766,6 +766,137 @@ var AllocateAddressExample = ` ` +// http://goo.gl/DFySJY +var DescribeInstanceStatusExample = ` + + 3be1508e-c444-4fef-89cc-0b1223c4f02fEXAMPLE + + + i-1a2b3c4d + us-east-1d + + 16 + running + + + impaired +
+ + reachability + failed + YYYY-MM-DDTHH:MM:SS.000Z + +
+
+ + impaired +
+ + reachability + failed + YYYY-MM-DDTHH:MM:SS.000Z + +
+
+ + + instance-retirement + The instance is running on degraded hardware + YYYY-MM-DDTHH:MM:SS+0000 + YYYY-MM-DDTHH:MM:SS+0000 + + +
+ + i-2a2b3c4d + us-east-1d + + 16 + running + + + ok +
+ + reachability + passed + +
+
+ + ok +
+ + reachability + passed + +
+
+ + + instance-reboot + The instance is scheduled for a reboot + YYYY-MM-DDTHH:MM:SS+0000 + YYYY-MM-DDTHH:MM:SS+0000 + + +
+ + i-3a2b3c4d + us-east-1c + + 16 + running + + + ok +
+ + reachability + passed + +
+
+ + ok +
+ + reachability + passed + +
+
+
+ + i-4a2b3c4d + us-east-1c + + 16 + running + + + ok +
+ + reachability + passed + +
+
+ + insufficient-data +
+ + reachability + insufficient-data + +
+
+
+
+
+` + // http://goo.gl/3Q0oCc var ReleaseAddressExample = ` @@ -845,6 +976,14 @@ var CreateSubnetExample = ` ` +// http://goo.gl/tu2Kxm +var ModifySubnetAttributeExample = ` + + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true + +` + // http://goo.gl/r6ZCPm var ResetImageAttributeExample = ` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb.go index 61a515d9..5d66f4a3 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb.go @@ -93,21 +93,28 @@ func makeParams(action string) map[string]string { type Listener struct { InstancePort int64 `xml:"member>Listener>InstancePort"` InstanceProtocol string `xml:"member>Listener>InstanceProtocol"` + SSLCertificateId string `xml:"member>Listener>SSLCertificateId"` LoadBalancerPort int64 `xml:"member>Listener>LoadBalancerPort"` Protocol string `xml:"member>Listener>Protocol"` } // An Instance attaches to an elb type Instance struct { - InstanceId string `xml:"member>InstanceId"` + InstanceId string `xml:"InstanceId"` +} + +// A tag attached to an elb +type Tag struct { + Key string `xml:"Key"` + Value string `xml:"Value"` } // An InstanceState from an elb health query type InstanceState struct { - InstanceId string `xml:"member>InstanceId"` - Description string `xml:"member>Description"` - State string `xml:"member>State"` - ReasonCode string `xml:"member>ReasonCode"` + InstanceId string `xml:"InstanceId"` + Description string `xml:"Description"` + State string `xml:"State"` + ReasonCode string `xml:"ReasonCode"` } // An Instance attaches to an elb @@ -115,6 +122,67 @@ type AvailabilityZone struct { AvailabilityZone string `xml:"member"` } +// ---------------------------------------------------------------------------- +// AddTags + +type AddTags struct { + LoadBalancerNames []string + Tags []Tag +} + +type AddTagsResp struct { + RequestId string `xml:"ResponseMetadata>RequestId"` +} + +func (elb *ELB) AddTags(options *AddTags) (resp *AddTagsResp, err error) { + params := makeParams("AddTags") + + for i, v := range options.LoadBalancerNames { + params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v + } + + for i, v := range options.Tags { + params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key + params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value + } + + resp = &AddTagsResp{} + + err = elb.query(params, resp) + + return resp, err +} + +// ---------------------------------------------------------------------------- +// RemoveTags + +type RemoveTags struct { + LoadBalancerNames []string + TagKeys []string +} + +type RemoveTagsResp struct { + RequestId string `xml:"ResponseMetadata>RequestId"` +} + +func (elb *ELB) RemoveTags(options *RemoveTags) (resp *RemoveTagsResp, err error) { + params := makeParams("RemoveTags") + + for i, v := range options.LoadBalancerNames { + params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v + } + + for i, v := range options.TagKeys { + params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v + } + + resp = &RemoveTagsResp{} + + err = elb.query(params, resp) + + return resp, err +} + // ---------------------------------------------------------------------------- // Create @@ -126,6 +194,7 @@ type CreateLoadBalancer struct { Internal bool // true for vpc elbs SecurityGroups []string Subnets []string + Tags []Tag } type CreateLoadBalancerResp struct { @@ -155,6 +224,12 @@ func (elb *ELB) CreateLoadBalancer(options *CreateLoadBalancer) (resp *CreateLoa params["Listeners.member."+strconv.Itoa(i+1)+".InstancePort"] = strconv.FormatInt(v.InstancePort, 10) params["Listeners.member."+strconv.Itoa(i+1)+".Protocol"] = v.Protocol params["Listeners.member."+strconv.Itoa(i+1)+".InstanceProtocol"] = v.InstanceProtocol + params["Listeners.member."+strconv.Itoa(i+1)+".SSLCertificateId"] = v.SSLCertificateId + } + + for i, v := range options.Tags { + params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key + params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value } if options.Internal { @@ -203,10 +278,13 @@ func (elb *ELB) DeleteLoadBalancer(options *DeleteLoadBalancer) (resp *SimpleRes type LoadBalancer struct { LoadBalancerName string `xml:"member>LoadBalancerName"` Listeners []Listener `xml:"member>ListenerDescriptions"` - Instances []Instance `xml:"member>Instances"` + Instances []Instance `xml:"member>Instances>member"` + HealthCheck HealthCheck `xml:"member>HealthCheck"` AvailabilityZones []AvailabilityZone `xml:"member>AvailabilityZones"` - Scheme string `xml:"member>Scheme"` DNSName string `xml:"member>DNSName"` + SecurityGroups []string `xml:"member>SecurityGroups>member"` + Scheme string `xml:"member>Scheme"` + Subnets []string `xml:"member>Subnets>member"` } // DescribeLoadBalancer request params @@ -302,6 +380,84 @@ func (elb *ELB) DeregisterInstancesFromLoadBalancer(options *DeregisterInstances return } +// ---------------------------------------------------------------------------- +// DescribeTags + +type DescribeTags struct { + LoadBalancerNames []string +} + +type LoadBalancerTag struct { + Tags []Tag `xml:"Tags>member"` + LoadBalancerName string `xml:"LoadBalancerName"` +} + +type DescribeTagsResp struct { + LoadBalancerTags []LoadBalancerTag `xml:"DescribeTagsResult>TagDescriptions>member"` + NextToken string `xml:"DescribeTagsResult>NextToken"` + RequestId string `xml:"ResponseMetadata>RequestId"` +} + +func (elb *ELB) DescribeTags(options *DescribeTags) (resp *DescribeTagsResp, err error) { + params := makeParams("DescribeTags") + + for i, v := range options.LoadBalancerNames { + params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v + } + + resp = &DescribeTagsResp{} + + err = elb.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + +// ---------------------------------------------------------------------------- +// Health Checks + +type HealthCheck struct { + HealthyThreshold int64 `xml:"HealthyThreshold"` + UnhealthyThreshold int64 `xml:"UnhealthyThreshold"` + Interval int64 `xml:"Interval"` + Target string `xml:"Target"` + Timeout int64 `xml:"Timeout"` +} + +type ConfigureHealthCheck struct { + LoadBalancerName string + Check HealthCheck +} + +type ConfigureHealthCheckResp struct { + Check HealthCheck `xml:"ConfigureHealthCheckResult>HealthCheck"` + RequestId string `xml:"ResponseMetadata>RequestId"` +} + +func (elb *ELB) ConfigureHealthCheck(options *ConfigureHealthCheck) (resp *ConfigureHealthCheckResp, err error) { + params := makeParams("ConfigureHealthCheck") + + params["LoadBalancerName"] = options.LoadBalancerName + params["HealthCheck.HealthyThreshold"] = strconv.Itoa(int(options.Check.HealthyThreshold)) + params["HealthCheck.UnhealthyThreshold"] = strconv.Itoa(int(options.Check.UnhealthyThreshold)) + params["HealthCheck.Interval"] = strconv.Itoa(int(options.Check.Interval)) + params["HealthCheck.Target"] = options.Check.Target + params["HealthCheck.Timeout"] = strconv.Itoa(int(options.Check.Timeout)) + + resp = &ConfigureHealthCheckResp{} + + err = elb.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + // ---------------------------------------------------------------------------- // Instance Health @@ -311,7 +467,7 @@ type DescribeInstanceHealth struct { } type DescribeInstanceHealthResp struct { - InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates"` + InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates>member"` RequestId string `xml:"ResponseMetadata>RequestId"` } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb_test.go index 3043e720..d0a5caab 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/elb_test.go @@ -30,6 +30,48 @@ func (s *S) TearDownTest(c *C) { testServer.Flush() } +func (s *S) TestAddTags(c *C) { + testServer.Response(200, nil, AddTagsExample) + + options := elb.AddTags{ + LoadBalancerNames: []string{"foobar"}, + Tags: []elb.Tag{ + { + Key: "hello", + Value: "world", + }, + }, + } + + resp, err := s.elb.AddTags(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"AddTags"}) + c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"}) + c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"}) + c.Assert(req.Form["Tags.member.1.Value"], DeepEquals, []string{"world"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "360e81f7-1100-11e4-b6ed-0f30EXAMPLE") +} + +func (s *S) TestRemoveTags(c *C) { + testServer.Response(200, nil, RemoveTagsExample) + + options := elb.RemoveTags{ + LoadBalancerNames: []string{"foobar"}, + TagKeys: []string{"hello"}, + } + + resp, err := s.elb.RemoveTags(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"RemoveTags"}) + c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"}) + c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE") +} + func (s *S) TestCreateLoadBalancer(c *C) { testServer.Response(200, nil, CreateLoadBalancerExample) @@ -38,6 +80,7 @@ func (s *S) TestCreateLoadBalancer(c *C) { Listeners: []elb.Listener{elb.Listener{ InstancePort: 80, InstanceProtocol: "http", + SSLCertificateId: "needToAddASSLCertToYourAWSAccount", LoadBalancerPort: 80, Protocol: "http", }, @@ -92,6 +135,11 @@ func (s *S) TestDescribeLoadBalancers(c *C) { c.Assert(resp.LoadBalancers[0].AvailabilityZones[0].AvailabilityZone, Equals, "us-east-1a") c.Assert(resp.LoadBalancers[0].Scheme, Equals, "internet-facing") c.Assert(resp.LoadBalancers[0].DNSName, Equals, "MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com") + c.Assert(resp.LoadBalancers[0].HealthCheck.HealthyThreshold, Equals, int64(2)) + c.Assert(resp.LoadBalancers[0].HealthCheck.UnhealthyThreshold, Equals, int64(10)) + c.Assert(resp.LoadBalancers[0].HealthCheck.Interval, Equals, int64(90)) + c.Assert(resp.LoadBalancers[0].HealthCheck.Target, Equals, "HTTP:80/") + c.Assert(resp.LoadBalancers[0].HealthCheck.Timeout, Equals, int64(60)) } func (s *S) TestRegisterInstancesWithLoadBalancer(c *C) { @@ -136,6 +184,35 @@ func (s *S) TestDeregisterInstancesFromLoadBalancer(c *C) { c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE") } +func (s *S) TestConfigureHealthCheck(c *C) { + testServer.Response(200, nil, ConfigureHealthCheckExample) + + options := elb.ConfigureHealthCheck{ + LoadBalancerName: "foobar", + Check: elb.HealthCheck{ + HealthyThreshold: 2, + UnhealthyThreshold: 2, + Interval: 30, + Target: "HTTP:80/ping", + Timeout: 3, + }, + } + + resp, err := s.elb.ConfigureHealthCheck(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"ConfigureHealthCheck"}) + c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"}) + c.Assert(err, IsNil) + + c.Assert(resp.Check.HealthyThreshold, Equals, int64(2)) + c.Assert(resp.Check.UnhealthyThreshold, Equals, int64(2)) + c.Assert(resp.Check.Interval, Equals, int64(30)) + c.Assert(resp.Check.Target, Equals, "HTTP:80/ping") + c.Assert(resp.Check.Timeout, Equals, int64(3)) + c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE") +} + func (s *S) TestDescribeInstanceHealth(c *C) { testServer.Response(200, nil, DescribeInstanceHealthExample) @@ -152,5 +229,7 @@ func (s *S) TestDescribeInstanceHealth(c *C) { c.Assert(resp.InstanceStates[0].InstanceId, Equals, "i-90d8c2a5") c.Assert(resp.InstanceStates[0].State, Equals, "InService") + c.Assert(resp.InstanceStates[1].InstanceId, Equals, "i-06ea3e60") + c.Assert(resp.InstanceStates[1].State, Equals, "OutOfService") c.Assert(resp.RequestId, Equals, "1549581b-12b7-11e3-895e-1334aEXAMPLE") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/responses_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/responses_test.go index 6b8578dc..77ae390c 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/responses_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/elb/responses_test.go @@ -7,6 +7,26 @@ var ErrorDump = ` 0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4 ` +// http://goo.gl/OkMdtJ +var AddTagsExample = ` + + + + 360e81f7-1100-11e4-b6ed-0f30EXAMPLE + + +` + +// http://goo.gl/nT2E89 +var RemoveTagsExample = ` + + + + 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE + + +` + // http://goo.gl/gQRD2H var CreateLoadBalancerExample = ` @@ -51,6 +71,7 @@ var DescribeLoadBalancersExample = ` HTTP 80 HTTP + needToAddASSLCertToYourAWSAccount 80 @@ -119,6 +140,23 @@ var DeregisterInstancesFromLoadBalancerExample = ` ` +// http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/API_ConfigureHealthCheck.html +var ConfigureHealthCheckExample = ` + + + + 30 + HTTP:80/ping + 2 + 3 + 2 + + + + 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE + +` + // http://goo.gl/cGNxfj var DescribeInstanceHealthExample = ` @@ -130,6 +168,12 @@ var DescribeInstanceHealthExample = ` InService N/A + + N/A + i-06ea3e60 + OutOfService + N/A + diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sdb/sign.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sdb/sign.go index 0f9c2348..cfb6706f 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sdb/sign.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sdb/sign.go @@ -4,11 +4,12 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" - "github.com/mitchellh/goamz/aws" "net/http" "net/url" "sort" "strings" + + "github.com/mitchellh/goamz/aws" ) var b64 = base64.StdEncoding @@ -31,7 +32,7 @@ func sign(auth aws.Auth, method, path string, params url.Values, headers http.He params["SignatureVersion"] = []string{"2"} params["SignatureMethod"] = []string{"HmacSHA256"} if auth.Token != "" { - params["SecurityToken"] = auth.Token + params["SecurityToken"] = []string{auth.Token} } // join up all the incoming params diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/permissions.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/permissions.go new file mode 100644 index 00000000..e7c73629 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/permissions.go @@ -0,0 +1,51 @@ +package sns + +import ( + "strconv" +) + +type Permission struct { + ActionName string + AccountId string +} + +type AddPermissionResponse struct { + ResponseMetadata +} + +// AddPermission +// +// See http://goo.gl/mbY4a for more details. +func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err error) { + resp = &AddPermissionResponse{} + params := makeParams("AddPermission") + + for i, p := range permissions { + params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId + params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName + } + + params["Label"] = Label + params["TopicArn"] = TopicArn + + err = sns.query(params, resp) + return +} + +type RemovePermissionResponse struct { + ResponseMetadata +} + +// RemovePermission +// +// See http://goo.gl/wGl5j for more details. +func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err error) { + resp = &RemovePermissionResponse{} + params := makeParams("RemovePermission") + + params["Label"] = Label + params["TopicArn"] = TopicArn + + err = sns.query(params, resp) + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns.go index 89e7b2f0..c1c08720 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns.go @@ -13,24 +13,16 @@ // first-class package in goamz. package sns -// BUG(niemeyer): Package needs significant clean up. - // BUG(niemeyer): Topic values in responses are not being initialized // properly, since they're supposed to reference *SNS. // BUG(niemeyer): Package needs documentation. -// BUG(niemeyer): Message.Message should be "Payload []byte" - -// BUG(niemeyer): Message.SNS must be dropped. - import ( "encoding/xml" - "errors" "github.com/mitchellh/goamz/aws" "net/http" "net/url" - "strconv" "time" ) @@ -41,32 +33,9 @@ type SNS struct { private byte // Reserve the right of using private data. } -type Topic struct { - SNS *SNS - TopicArn string -} - -func New(auth aws.Auth, region aws.Region) *SNS { - return &SNS{auth, region, 0} -} - -type Message struct { - SNS *SNS - Topic *Topic - Message [8192]byte - Subject string -} - -type Subscription struct { - Endpoint string - Owner string - Protocol string - SubscriptionArn string - TopicArn string -} - -func (topic *Topic) Message(message [8192]byte, subject string) *Message { - return &Message{topic.SNS, topic, message, subject} +type AttributeEntry struct { + Key string `xml:"key"` + Value string `xml:"value"` } type ResponseMetadata struct { @@ -74,35 +43,8 @@ type ResponseMetadata struct { BoxUsage float64 `xml:"ResponseMetadata>BoxUsage"` } -type ListTopicsResp struct { - Topics []Topic `xml:"ListTopicsResult>Topics>member"` - NextToken string - ResponseMetadata -} - -type CreateTopicResp struct { - Topic Topic `xml:"CreateTopicResult"` - ResponseMetadata -} - -type DeleteTopicResp struct { - ResponseMetadata -} - -type ListSubscriptionsResp struct { - Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"` - NextToken string - ResponseMetadata -} - -type AttributeEntry struct { - Key string `xml:"key"` - Value string `xml:"value"` -} - -type GetTopicAttributesResp struct { - Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"` - ResponseMetadata +func New(auth aws.Auth, region aws.Region) *SNS { + return &SNS{auth, region, 0} } func makeParams(action string) map[string]string { @@ -111,273 +53,6 @@ func makeParams(action string) map[string]string { return params } -// ListTopics -// -// See http://goo.gl/lfrMK for more details. -func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResp, err error) { - resp = &ListTopicsResp{} - params := makeParams("ListTopics") - if NextToken != nil { - params["NextToken"] = *NextToken - } - err = sns.query(nil, nil, params, resp) - return -} - -// CreateTopic -// -// See http://goo.gl/m9aAt for more details. -func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResp, err error) { - resp = &CreateTopicResp{} - params := makeParams("CreateTopic") - params["Name"] = Name - err = sns.query(nil, nil, params, resp) - return -} - -// DeleteTopic -// -// See http://goo.gl/OXNcY for more details. -func (sns *SNS) DeleteTopic(topic Topic) (resp *DeleteTopicResp, err error) { - resp = &DeleteTopicResp{} - params := makeParams("DeleteTopic") - params["TopicArn"] = topic.TopicArn - err = sns.query(nil, nil, params, resp) - return -} - -// Delete -// -// Helper function for deleting a topic -func (topic *Topic) Delete() (resp *DeleteTopicResp, err error) { - return topic.SNS.DeleteTopic(*topic) -} - -// ListSubscriptions -// -// See http://goo.gl/k3aGn for more details. -func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResp, err error) { - resp = &ListSubscriptionsResp{} - params := makeParams("ListSubscriptions") - if NextToken != nil { - params["NextToken"] = *NextToken - } - err = sns.query(nil, nil, params, resp) - return -} - -// GetTopicAttributes -// -// See http://goo.gl/WXRoX for more details. -func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResp, err error) { - resp = &GetTopicAttributesResp{} - params := makeParams("GetTopicAttributes") - params["TopicArn"] = TopicArn - err = sns.query(nil, nil, params, resp) - return -} - -type PublishOpt struct { - Message string - MessageStructure string - Subject string - TopicArn string -} - -type PublishResp struct { - MessageId string `xml:"PublishResult>MessageId"` - ResponseMetadata -} - -// Publish -// -// See http://goo.gl/AY2D8 for more details. -func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResp, err error) { - resp = &PublishResp{} - params := makeParams("Publish") - - if options.Subject != "" { - params["Subject"] = options.Subject - } - - if options.MessageStructure != "" { - params["MessageStructure"] = options.MessageStructure - } - - if options.Message != "" { - params["Message"] = options.Message - } - - if options.TopicArn != "" { - params["TopicArn"] = options.TopicArn - } - - err = sns.query(nil, nil, params, resp) - return -} - -type SetTopicAttributesResponse struct { - ResponseMetadata -} - -// SetTopicAttributes -// -// See http://goo.gl/oVYW7 for more details. -func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err error) { - resp = &SetTopicAttributesResponse{} - params := makeParams("SetTopicAttributes") - - if AttributeName == "" || TopicArn == "" { - return nil, errors.New("Invalid Attribute Name or TopicArn") - } - - params["AttributeName"] = AttributeName - params["AttributeValue"] = AttributeValue - params["TopicArn"] = TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - -type SubscribeResponse struct { - SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"` - ResponseMetadata -} - -// Subscribe -// -// See http://goo.gl/c3iGS for more details. -func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err error) { - resp = &SubscribeResponse{} - params := makeParams("Subscribe") - - params["Endpoint"] = Endpoint - params["Protocol"] = Protocol - params["TopicArn"] = TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - -type UnsubscribeResponse struct { - ResponseMetadata -} - -// Unsubscribe -// -// See http://goo.gl/4l5Ge for more details. -func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err error) { - resp = &UnsubscribeResponse{} - params := makeParams("Unsubscribe") - - params["SubscriptionArn"] = SubscriptionArn - - err = sns.query(nil, nil, params, resp) - return -} - -type ConfirmSubscriptionResponse struct { - SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"` - ResponseMetadata -} - -type ConfirmSubscriptionOpt struct { - AuthenticateOnUnsubscribe string - Token string - TopicArn string -} - -// ConfirmSubscription -// -// See http://goo.gl/3hXzH for more details. -func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err error) { - resp = &ConfirmSubscriptionResponse{} - params := makeParams("ConfirmSubscription") - - if options.AuthenticateOnUnsubscribe != "" { - params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe - } - - params["Token"] = options.Token - params["TopicArn"] = options.TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - -type Permission struct { - ActionName string - AccountId string -} - -type AddPermissionResponse struct { - ResponseMetadata -} - -// AddPermission -// -// See http://goo.gl/mbY4a for more details. -func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err error) { - resp = &AddPermissionResponse{} - params := makeParams("AddPermission") - - for i, p := range permissions { - params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId - params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName - } - - params["Label"] = Label - params["TopicArn"] = TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - -type RemovePermissionResponse struct { - ResponseMetadata -} - -// RemovePermission -// -// See http://goo.gl/wGl5j for more details. -func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err error) { - resp = &RemovePermissionResponse{} - params := makeParams("RemovePermission") - - params["Label"] = Label - params["TopicArn"] = TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - -type ListSubscriptionByTopicResponse struct { - Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"` - ResponseMetadata -} - -type ListSubscriptionByTopicOpt struct { - NextToken string - TopicArn string -} - -// ListSubscriptionByTopic -// -// See http://goo.gl/LaVcC for more details. -func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err error) { - resp = &ListSubscriptionByTopicResponse{} - params := makeParams("ListSbubscriptionByTopic") - - if options.NextToken != "" { - params["NextToken"] = options.NextToken - } - - params["TopicArn"] = options.TopicArn - - err = sns.query(nil, nil, params, resp) - return -} - type Error struct { StatusCode int Code string @@ -394,7 +69,7 @@ type xmlErrors struct { Errors []Error `xml:"Errors>Error"` } -func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, resp interface{}) error { +func (sns *SNS) query(params map[string]string, resp interface{}) error { params["Timestamp"] = time.Now().UTC().Format(time.RFC3339) u, err := url.Parse(sns.Region.SNSEndpoint) if err != nil { @@ -409,10 +84,6 @@ func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, } defer r.Body.Close() - //dump, _ := http.DumpResponse(r, true) - //println("DUMP:\n", string(dump)) - //return nil - if r.StatusCode != 200 { return buildError(r) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns_test.go index a8ff979c..3eb97f4a 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/sns_test.go @@ -40,6 +40,7 @@ func (s *S) TestListTopicsOK(c *C) { c.Assert(req.URL.Path, Equals, "/") c.Assert(req.Header["Date"], Not(Equals), "") + c.Assert(resp.Topics[0].SNS, Equals, s.sns) c.Assert(resp.ResponseMetadata.RequestId, Equals, "bd10b26c-e30e-11e0-ba29-93c3aca2f103") c.Assert(err, IsNil) } @@ -54,6 +55,7 @@ func (s *S) TestCreateTopic(c *C) { c.Assert(req.URL.Path, Equals, "/") c.Assert(req.Header["Date"], Not(Equals), "") + c.Assert(resp.Topic.SNS, Equals, s.sns) c.Assert(resp.Topic.TopicArn, Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic") c.Assert(resp.ResponseMetadata.RequestId, Equals, "a8dec8b3-33a4-11df-8963-01868b7c937a") c.Assert(err, IsNil) @@ -62,8 +64,8 @@ func (s *S) TestCreateTopic(c *C) { func (s *S) TestDeleteTopic(c *C) { testServer.Response(200, nil, TestDeleteTopicXmlOK) - t := sns.Topic{nil, "arn:aws:sns:us-east-1:123456789012:My-Topic"} - resp, err := s.sns.DeleteTopic(t) + t := sns.Topic{s.sns, "arn:aws:sns:us-east-1:123456789012:My-Topic"} + resp, err := t.Delete() req := testServer.WaitRequest() c.Assert(req.Method, Equals, "GET") @@ -115,7 +117,14 @@ func (s *S) TestGetTopicAttributes(c *C) { func (s *S) TestPublish(c *C) { testServer.Response(200, nil, TestPublishXmlOK) - pubOpt := &sns.PublishOpt{"foobar", "", "subject", "arn:aws:sns:us-east-1:123456789012:My-Topic"} + pubOpt := &sns.PublishOpt{ + Message: "foobar", + MessageStructure: "", + Subject: "subject", + TopicArn: "arn:aws:sns:us-east-1:123456789012:My-Topic", + TargetArn: "arn:aws:sns:us-east-1:123456789012:My-Other-Topic", + } + resp, err := s.sns.Publish(pubOpt) req := testServer.WaitRequest() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/subscription.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/subscription.go new file mode 100644 index 00000000..6110bb85 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/subscription.go @@ -0,0 +1,161 @@ +package sns + +type Subscription struct { + Endpoint string + Owner string + Protocol string + SubscriptionArn string + TopicArn string +} + +type ListSubscriptionsResp struct { + Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"` + NextToken string + ResponseMetadata +} + +type PublishOpt struct { + Message string + MessageStructure string + Subject string + TopicArn string + TargetArn string +} + +type PublishResp struct { + MessageId string `xml:"PublishResult>MessageId"` + ResponseMetadata +} + +type SubscribeResponse struct { + SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"` + ResponseMetadata +} + +type UnsubscribeResponse struct { + ResponseMetadata +} + +type ConfirmSubscriptionResponse struct { + SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"` + ResponseMetadata +} + +type ConfirmSubscriptionOpt struct { + AuthenticateOnUnsubscribe string + Token string + TopicArn string +} + +type ListSubscriptionByTopicResponse struct { + Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"` + ResponseMetadata +} + +type ListSubscriptionByTopicOpt struct { + NextToken string + TopicArn string +} + +// Publish +// +// See http://goo.gl/AY2D8 for more details. +func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResp, err error) { + resp = &PublishResp{} + params := makeParams("Publish") + + if options.Subject != "" { + params["Subject"] = options.Subject + } + + if options.MessageStructure != "" { + params["MessageStructure"] = options.MessageStructure + } + + if options.Message != "" { + params["Message"] = options.Message + } + + if options.TopicArn != "" { + params["TopicArn"] = options.TopicArn + } + + err = sns.query(params, resp) + return +} + +// Subscribe +// +// See http://goo.gl/c3iGS for more details. +func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err error) { + resp = &SubscribeResponse{} + params := makeParams("Subscribe") + + params["Endpoint"] = Endpoint + params["Protocol"] = Protocol + params["TopicArn"] = TopicArn + + err = sns.query(params, resp) + return +} + +// Unsubscribe +// +// See http://goo.gl/4l5Ge for more details. +func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err error) { + resp = &UnsubscribeResponse{} + params := makeParams("Unsubscribe") + + params["SubscriptionArn"] = SubscriptionArn + + err = sns.query(params, resp) + return +} + +// ConfirmSubscription +// +// See http://goo.gl/3hXzH for more details. +func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err error) { + resp = &ConfirmSubscriptionResponse{} + params := makeParams("ConfirmSubscription") + + if options.AuthenticateOnUnsubscribe != "" { + params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe + } + + params["Token"] = options.Token + params["TopicArn"] = options.TopicArn + + err = sns.query(params, resp) + return +} + +// ListSubscriptions +// +// See http://goo.gl/k3aGn for more details. +func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResp, err error) { + resp = &ListSubscriptionsResp{} + params := makeParams("ListSubscriptions") + if NextToken != nil { + params["NextToken"] = *NextToken + } + err = sns.query(params, resp) + return +} + +// ListSubscriptionByTopic +// +// See http://goo.gl/LaVcC for more details. +func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err error) { + resp = &ListSubscriptionByTopicResponse{} + params := makeParams("ListSbubscriptionByTopic") + + if options.NextToken != "" { + params["NextToken"] = options.NextToken + } + + params["TopicArn"] = options.TopicArn + + err = sns.query(params, resp) + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/topic.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/topic.go new file mode 100644 index 00000000..4d82cbfb --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/exp/sns/topic.go @@ -0,0 +1,103 @@ +package sns + +import ( + "errors" +) + +type Topic struct { + SNS *SNS + TopicArn string +} + +type ListTopicsResp struct { + Topics []Topic `xml:"ListTopicsResult>Topics>member"` + NextToken string + ResponseMetadata +} + +type CreateTopicResp struct { + Topic Topic `xml:"CreateTopicResult"` + ResponseMetadata +} + +type DeleteTopicResp struct { + ResponseMetadata +} + +type GetTopicAttributesResp struct { + Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"` + ResponseMetadata +} + +type SetTopicAttributesResponse struct { + ResponseMetadata +} + +// ListTopics +// +// See http://goo.gl/lfrMK for more details. +func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResp, err error) { + resp = &ListTopicsResp{} + params := makeParams("ListTopics") + if NextToken != nil { + params["NextToken"] = *NextToken + } + err = sns.query(params, resp) + for i, _ := range resp.Topics { + resp.Topics[i].SNS = sns + } + return +} + +// CreateTopic +// +// See http://goo.gl/m9aAt for more details. +func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResp, err error) { + resp = &CreateTopicResp{} + params := makeParams("CreateTopic") + params["Name"] = Name + err = sns.query(params, resp) + resp.Topic.SNS = sns + return +} + +// Delete +// +// Helper function for deleting a topic +func (topic *Topic) Delete() (resp *DeleteTopicResp, err error) { + resp = &DeleteTopicResp{} + params := makeParams("DeleteTopic") + params["TopicArn"] = topic.TopicArn + err = topic.SNS.query(params, resp) + return +} + +// GetTopicAttributes +// +// See http://goo.gl/WXRoX for more details. +func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResp, err error) { + resp = &GetTopicAttributesResp{} + params := makeParams("GetTopicAttributes") + params["TopicArn"] = TopicArn + err = sns.query(params, resp) + return +} + +// SetTopicAttributes +// +// See http://goo.gl/oVYW7 for more details. +func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err error) { + resp = &SetTopicAttributesResponse{} + params := makeParams("SetTopicAttributes") + + if AttributeName == "" || TopicArn == "" { + return nil, errors.New("Invalid Attribute Name or TopicArn") + } + + params["AttributeName"] = AttributeName + params["AttributeValue"] = AttributeValue + params["TopicArn"] = TopicArn + + err = sns.query(params, resp) + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds.go index 05933227..e2dbb72b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds.go @@ -90,23 +90,24 @@ func makeParams(action string) map[string]string { // Rds objects type DBInstance struct { - Address string `xml:"Endpoint>Address"` - AllocatedStorage int `xml:"AllocatedStorage"` - AvailabilityZone string `xml:"AvailabilityZone"` - BackupRetentionPeriod int `xml:"BackupRetentionPeriod"` - DBInstanceClass string `xml:"DBInstanceClass"` - DBInstanceIdentifier string `xml:"DBInstanceIdentifier"` - DBInstanceStatus string `xml:"DBInstanceStatus"` - DBName string `xml:"DBName"` - Engine string `xml:"Engine"` - EngineVersion string `xml:"EngineVersion"` - MasterUsername string `xml:"MasterUsername"` - MultiAZ bool `xml:"MultiAZ"` - Port int `xml:"Endpoint>Port"` - PreferredBackupWindow string `xml:"PreferredBackupWindow"` - PreferredMaintenanceWindow string `xml:"PreferredMaintenanceWindow"` - VpcSecurityGroupIds []string `xml:"VpcSecurityGroups"` - DBSecurityGroupNames []string `xml:"DBSecurityGroups>DBSecurityGroup>DBSecurityGroupName"` + Address string `xml:"Endpoint>Address"` + AllocatedStorage int `xml:"AllocatedStorage"` + AvailabilityZone string `xml:"AvailabilityZone"` + BackupRetentionPeriod int `xml:"BackupRetentionPeriod"` + DBInstanceClass string `xml:"DBInstanceClass"` + DBInstanceIdentifier string `xml:"DBInstanceIdentifier"` + DBInstanceStatus string `xml:"DBInstanceStatus"` + DBName string `xml:"DBName"` + Engine string `xml:"Engine"` + EngineVersion string `xml:"EngineVersion"` + MasterUsername string `xml:"MasterUsername"` + MultiAZ bool `xml:"MultiAZ"` + Port int `xml:"Endpoint>Port"` + PreferredBackupWindow string `xml:"PreferredBackupWindow"` + PreferredMaintenanceWindow string `xml:"PreferredMaintenanceWindow"` + VpcSecurityGroupIds []string `xml:"VpcSecurityGroups"` + DBSecurityGroupNames []string `xml:"DBSecurityGroups>DBSecurityGroup>DBSecurityGroupName"` + DBSubnetGroup DBSubnetGroup `xml:"DBSubnetGroup"` } type DBSecurityGroup struct { @@ -119,6 +120,35 @@ type DBSecurityGroup struct { CidrStatuses []string `xml:"IPRanges>IPRange>Status"` } +type DBSubnetGroup struct { + Description string `xml:"DBSubnetGroupDescription"` + Name string `xml:"DBSubnetGroupName"` + Status string `xml:"SubnetGroupStatus"` + SubnetIds []string `xml:"Subnets>Subnet>SubnetIdentifier"` + VpcId string `xml:"VpcId"` +} + +type DBSnapshot struct { + AllocatedStorage int `xml:"AllocatedStorage"` + AvailabilityZone string `xml:"AvailabilityZone"` + DBInstanceIdentifier string `xml:"DBInstanceIdentifier"` + DBSnapshotIdentifier string `xml:"DBSnapshotIdentifier"` + Engine string `xml:"Engine"` + EngineVersion string `xml:"EngineVersion"` + InstanceCreateTime string `xml:"InstanceCreateTime"` + Iops int `xml:"Iops"` + LicenseModel string `xml:"LicenseModel"` + MasterUsername string `xml:"MasterUsername"` + OptionGroupName string `xml:"OptionGroupName"` + PercentProgress int `xml:"PercentProgress"` + Port int `xml:"Port"` + SnapshotCreateTime string `xml:"SnapshotCreateTime"` + SnapshotType string `xml:"SnapshotType"` + SourceRegion string `xml:"SourceRegion"` + Status string `xml:"Status"` + VpcId string `xml:"VpcId"` +} + // ---------------------------------------------------------------------------- // Create @@ -194,7 +224,7 @@ func (rds *Rds) CreateDBInstance(options *CreateDBInstance) (resp *SimpleResp, e } if options.EngineVersion != "" { - params["Engine"] = options.EngineVersion + params["EngineVersion"] = options.EngineVersion } if options.MasterUsername != "" { @@ -263,6 +293,34 @@ func (rds *Rds) CreateDBSecurityGroup(options *CreateDBSecurityGroup) (resp *Sim return } +// The CreateDBSubnetGroup request parameters +type CreateDBSubnetGroup struct { + DBSubnetGroupName string + DBSubnetGroupDescription string + SubnetIds []string +} + +func (rds *Rds) CreateDBSubnetGroup(options *CreateDBSubnetGroup) (resp *SimpleResp, err error) { + params := makeParams("CreateDBSubnetGroup") + + params["DBSubnetGroupName"] = options.DBSubnetGroupName + params["DBSubnetGroupDescription"] = options.DBSubnetGroupDescription + + for j, group := range options.SubnetIds { + params["SubnetIds.member."+strconv.Itoa(j+1)] = group + } + + resp = &SimpleResp{} + + err = rds.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + // The CreateDBSecurityGroup request parameters type AuthorizeDBSecurityGroupIngress struct { Cidr string @@ -358,10 +416,75 @@ func (rds *Rds) DescribeDBSecurityGroups(options *DescribeDBSecurityGroups) (res return } +// DescribeDBSubnetGroups request params +type DescribeDBSubnetGroups struct { + DBSubnetGroupName string +} + +type DescribeDBSubnetGroupsResp struct { + RequestId string `xml:"ResponseMetadata>RequestId"` + DBSubnetGroups []DBSubnetGroup `xml:"DescribeDBSubnetGroupsResult>DBSubnetGroups>DBSubnetGroup"` +} + +func (rds *Rds) DescribeDBSubnetGroups(options *DescribeDBSubnetGroups) (resp *DescribeDBSubnetGroupsResp, err error) { + params := makeParams("DescribeDBSubnetGroups") + + params["DBSubnetGroupName"] = options.DBSubnetGroupName + + resp = &DescribeDBSubnetGroupsResp{} + + err = rds.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + +// DescribeDBSnapshots request params +type DescribeDBSnapshots struct { + DBInstanceIdentifier string + DBSnapshotIdentifier string + SnapshotType string +} + +type DescribeDBSnapshotsResp struct { + RequestId string `xml:"ResponseMetadata>RequestId"` + DBSnapshots []DBSnapshot `xml:"DescribeDBSnapshotsResult>DBSnapshots>DBSnapshot"` +} + +func (rds *Rds) DescribeDBSnapshots(options *DescribeDBSnapshots) (resp *DescribeDBSnapshotsResp, err error) { + params := makeParams("DescribeDBSnapshots") + + if options.DBInstanceIdentifier != "" { + params["DBInstanceIdentifier"] = options.DBInstanceIdentifier + } + + if options.DBSnapshotIdentifier != "" { + params["DBSnapshotIdentifier"] = options.DBSnapshotIdentifier + } + + if options.SnapshotType != "" { + params["SnapshotType"] = options.SnapshotType + } + + resp = &DescribeDBSnapshotsResp{} + + err = rds.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + // DeleteDBInstance request params type DeleteDBInstance struct { - DBInstanceIdentifier string - SkipFinalSnapshot bool + FinalDBSnapshotIdentifier string + DBInstanceIdentifier string + SkipFinalSnapshot bool } func (rds *Rds) DeleteDBInstance(options *DeleteDBInstance) (resp *SimpleResp, err error) { @@ -369,8 +492,12 @@ func (rds *Rds) DeleteDBInstance(options *DeleteDBInstance) (resp *SimpleResp, e params["DBInstanceIdentifier"] = options.DBInstanceIdentifier + // If we don't skip the final snapshot, we need to specify a final + // snapshot identifier if options.SkipFinalSnapshot { params["SkipFinalSnapshot"] = "true" + } else { + params["FinalDBSnapshotIdentifier"] = options.FinalDBSnapshotIdentifier } resp = &SimpleResp{} @@ -405,6 +532,112 @@ func (rds *Rds) DeleteDBSecurityGroup(options *DeleteDBSecurityGroup) (resp *Sim return } +// DeleteDBSubnetGroup request params +type DeleteDBSubnetGroup struct { + DBSubnetGroupName string +} + +func (rds *Rds) DeleteDBSubnetGroup(options *DeleteDBSubnetGroup) (resp *SimpleResp, err error) { + params := makeParams("DeleteDBSubnetGroup") + + params["DBSubnetGroupName"] = options.DBSubnetGroupName + + resp = &SimpleResp{} + + err = rds.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + +type RestoreDBInstanceFromDBSnapshot struct { + DBInstanceIdentifier string + DBSnapshotIdentifier string + AutoMinorVersionUpgrade bool + AvailabilityZone string + DBInstanceClass string + DBName string + DBSubnetGroupName string + Engine string + Iops int + LicenseModel string + MultiAZ bool + OptionGroupName string + Port int + PubliclyAccessible bool + + SetIops bool + SetPort bool +} + +func (rds *Rds) RestoreDBInstanceFromDBSnapshot(options *RestoreDBInstanceFromDBSnapshot) (resp *SimpleResp, err error) { + params := makeParams("RestoreDBInstanceFromDBSnapshot") + + params["DBInstanceIdentifier"] = options.DBInstanceIdentifier + params["DBSnapshotIdentifier"] = options.DBSnapshotIdentifier + + if options.AutoMinorVersionUpgrade { + params["AutoMinorVersionUpgrade"] = "true" + } + + if options.AvailabilityZone != "" { + params["AvailabilityZone"] = options.AvailabilityZone + } + + if options.DBInstanceClass != "" { + params["DBInstanceClass"] = options.DBInstanceClass + } + + if options.DBName != "" { + params["DBName"] = options.DBName + } + + if options.DBSubnetGroupName != "" { + params["DBSubnetGroupName"] = options.DBSubnetGroupName + } + + if options.Engine != "" { + params["Engine"] = options.Engine + } + + if options.SetIops { + params["Iops"] = strconv.Itoa(options.Iops) + } + + if options.LicenseModel != "" { + params["LicenseModel"] = options.LicenseModel + } + + if options.MultiAZ { + params["MultiAZ"] = "true" + } + + if options.OptionGroupName != "" { + params["OptionGroupName"] = options.OptionGroupName + } + + if options.SetPort { + params["Port"] = strconv.Itoa(options.Port) + } + + if options.PubliclyAccessible { + params["PubliclyAccessible"] = "true" + } + + resp = &SimpleResp{} + + err = rds.query(params, resp) + + if err != nil { + resp = nil + } + + return +} + // Responses type SimpleResp struct { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds_test.go index 2ea4e06c..0ccadb7c 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/rds_test.go @@ -80,6 +80,27 @@ func (s *S) Test_CreateDBSecurityGroup(c *C) { c.Assert(resp.RequestId, Equals, "e68ef6fa-afc1-11c3-845a-476777009d19") } +func (s *S) Test_CreateDBSubnetGroup(c *C) { + testServer.Response(200, nil, CreateDBSubnetGroupExample) + + options := rds.CreateDBSubnetGroup{ + DBSubnetGroupName: "foobarbaz", + DBSubnetGroupDescription: "test description", + SubnetIds: []string{"subnet-e4d398a1", "subnet-c2bdb6ba"}, + } + + resp, err := s.rds.CreateDBSubnetGroup(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"CreateDBSubnetGroup"}) + c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"}) + c.Assert(req.Form["DBSubnetGroupDescription"], DeepEquals, []string{"test description"}) + c.Assert(req.Form["SubnetIds.member.1"], DeepEquals, []string{"subnet-e4d398a1"}) + c.Assert(req.Form["SubnetIds.member.2"], DeepEquals, []string{"subnet-c2bdb6ba"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "3a401b3f-bb9e-11d3-f4c6-37db295f7674") +} + func (s *S) Test_DescribeDBInstances(c *C) { testServer.Response(200, nil, DescribeDBInstancesExample) @@ -119,6 +140,25 @@ func (s *S) Test_DescribeDBSecurityGroups(c *C) { c.Assert(resp.DBSecurityGroups[0].CidrStatuses, DeepEquals, []string{"authorized", "authorized", "authorized", "authorized"}) } +func (s *S) Test_DescribeDBSubnetGroups(c *C) { + testServer.Response(200, nil, DescribeDBSubnetGroupsExample) + + options := rds.DescribeDBSubnetGroups{ + DBSubnetGroupName: "foobarbaz", + } + + resp, err := s.rds.DescribeDBSubnetGroups(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeDBSubnetGroups"}) + c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "b783db3b-b98c-11d3-fbc7-5c0aad74da7c") + c.Assert(resp.DBSubnetGroups[0].Status, DeepEquals, "Complete") + c.Assert(resp.DBSubnetGroups[0].SubnetIds, DeepEquals, []string{"subnet-e8b3e5b1", "subnet-44b2f22e"}) + c.Assert(resp.DBSubnetGroups[0].VpcId, DeepEquals, "vpc-e7abbdce") +} + func (s *S) Test_DeleteDBInstance(c *C) { testServer.Response(200, nil, DeleteDBInstanceExample) @@ -137,6 +177,26 @@ func (s *S) Test_DeleteDBInstance(c *C) { c.Assert(resp.RequestId, Equals, "7369556f-b70d-11c3-faca-6ba18376ea1b") } +func (s *S) Test_DeleteDBInstance_SnapshotIdentifier(c *C) { + testServer.Response(200, nil, DeleteDBInstanceExample) + + options := rds.DeleteDBInstance{ + DBInstanceIdentifier: "foobarbaz", + SkipFinalSnapshot: false, + FinalDBSnapshotIdentifier: "bar", + } + + resp, err := s.rds.DeleteDBInstance(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteDBInstance"}) + c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foobarbaz"}) + c.Assert(req.Form["FinalDBSnapshotIdentifier"], DeepEquals, []string{"bar"}) + c.Assert(req.Form["SkipFinalSnapshot"], IsNil) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "7369556f-b70d-11c3-faca-6ba18376ea1b") +} + func (s *S) Test_DeleteDBSecurityGroup(c *C) { testServer.Response(200, nil, DeleteDBSecurityGroupExample) @@ -153,6 +213,22 @@ func (s *S) Test_DeleteDBSecurityGroup(c *C) { c.Assert(resp.RequestId, Equals, "7aec7454-ba25-11d3-855b-576787000e19") } +func (s *S) Test_DeleteDBSubnetGroup(c *C) { + testServer.Response(200, nil, DeleteDBSubnetGroupExample) + + options := rds.DeleteDBSubnetGroup{ + DBSubnetGroupName: "foobarbaz", + } + + resp, err := s.rds.DeleteDBSubnetGroup(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteDBSubnetGroup"}) + c.Assert(req.Form["DBSubnetGroupName"], DeepEquals, []string{"foobarbaz"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "6295e5ab-bbf3-11d3-f4c6-37db295f7674") +} + func (s *S) Test_AuthorizeDBSecurityGroupIngress(c *C) { testServer.Response(200, nil, AuthorizeDBSecurityGroupIngressExample) @@ -170,3 +246,44 @@ func (s *S) Test_AuthorizeDBSecurityGroupIngress(c *C) { c.Assert(err, IsNil) c.Assert(resp.RequestId, Equals, "6176b5f8-bfed-11d3-f92b-31fa5e8dbc99") } + +func (s *S) Test_DescribeDBSnapshots(c *C) { + testServer.Response(200, nil, DescribeDBSnapshotsExample) + + options := rds.DescribeDBSnapshots{ + DBInstanceIdentifier: "foobar", + DBSnapshotIdentifier: "baz", + SnapshotType: "manual", + } + + resp, err := s.rds.DescribeDBSnapshots(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeDBSnapshots"}) + c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foobar"}) + c.Assert(req.Form["DBSnapshotIdentifier"], DeepEquals, []string{"baz"}) + c.Assert(req.Form["SnapshotType"], DeepEquals, []string{"manual"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "b7769930-b98c-11d3-f272-7cd6cce12cc5") + c.Assert(resp.DBSnapshots[0].OptionGroupName, Equals, "default:mysql-5-6") + c.Assert(resp.DBSnapshots[0].Engine, Equals, "mysql") + c.Assert(resp.DBSnapshots[0].SnapshotType, Equals, "manual") +} + +func (s *S) Test_RestoreDBInstanceFromDBSnapshot(c *C) { + testServer.Response(200, nil, RestoreDBInstanceFromDBSnapshotExample) + + options := rds.RestoreDBInstanceFromDBSnapshot{ + DBInstanceIdentifier: "foo", + DBSnapshotIdentifier: "bar", + } + + resp, err := s.rds.RestoreDBInstanceFromDBSnapshot(&options) + req := testServer.WaitRequest() + + c.Assert(req.Form["Action"], DeepEquals, []string{"RestoreDBInstanceFromDBSnapshot"}) + c.Assert(req.Form["DBInstanceIdentifier"], DeepEquals, []string{"foo"}) + c.Assert(req.Form["DBSnapshotIdentifier"], DeepEquals, []string{"bar"}) + c.Assert(err, IsNil) + c.Assert(resp.RequestId, Equals, "863fd73e-be2b-11d3-855b-576787000e19") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/responses_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/responses_test.go index 418fc6c2..39a1e771 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/responses_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/rds/responses_test.go @@ -327,3 +327,224 @@ var AuthorizeDBSecurityGroupIngressExample = ` ` + +var DescribeDBSubnetGroupsExample = ` + + + + + vpc-e7abbdce + Complete + DB subnet group 1 + mydbsubnetgroup1 + + + Active + subnet-e8b3e5b1 + + us-west-2a + false + + + + Active + subnet-44b2f22e + + us-west-2b + false + + + + + + vpc-c1e17bb8 + Complete + My DB Subnet Group 2 + sub-grp-2 + + + Active + subnet-d281ef8a + + us-west-2a + false + + + + Active + subnet-b381ef9f + + us-west-2c + false + + + + Active + subnet-e1e17ebd + + us-west-2b + false + + + + + + + + b783db3b-b98c-11d3-fbc7-5c0aad74da7c + + +` + +var DeleteDBSubnetGroupExample = ` + + + 6295e5ab-bbf3-11d3-f4c6-37db295f7674 + + +` + +var CreateDBSubnetGroupExample = ` + + + + vpc-33dc97ea + Complete + My new DB Subnet Group + myawsuser-dbsubnetgroup + + + Active + subnet-e4d398a1 + + us-east-1b + false + + + + Active + subnet-c2bdb6ba + + us-east-1c + false + + + + + + + 3a401b3f-bb9e-11d3-f4c6-37db295f7674 + + +` + +var DescribeDBSnapshotsExample = ` + + + + + 3306 + default:mysql-5-6 + mysql + available + manual + general-public-license + 5.6.13 + my-mysqlexampledb + my-test-restore-snapshot + 2014-03-28T19:57:16.707Z + us-west-2b + 2014-01-29T22:58:24.231Z + 100 + 5 + awsmyuser + + + 3306 + default:mysql-5-6 + mysql + available + automated + general-public-license + 5.6.13 + my-mysqlexampledb + rds:my-mysqlexampledb-2014-04-19-10-08 + 2014-04-19T10:09:09.790Z + us-west-2b + 2014-01-29T22:58:24.231Z + 100 + 5 + awsmyuser + + + 3306 + default:mysql-5-6 + mysql + available + automated + general-public-license + 5.6.13 + my-mysqlexampledb + rds:my-mysqlexampledb-2014-04-20-10-09 + 2014-04-20T10:09:15.446Z + us-west-2b + 2014-01-29T22:58:24.231Z + 100 + 5 + awsmyuser + + + + + b7769930-b98c-11d3-f272-7cd6cce12cc5 + + +` + +var RestoreDBInstanceFromDBSnapshotExample = ` + + + + 2 + false + creating + + mysqldb-restored + 08:14-08:44 + fri:04:50-fri:05:20 + + mysql + + general-public-license + 5.6.13 + + + in-sync + default.mysql5.6 + + + + + default:mysql-5-6 + pending-apply + + + true + + + active + default + + + mysqldb + true + 100 + myawsuser + db.m1.medium + + + + 863fd73e-be2b-11d3-855b-576787000e19 + + +` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/responses_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/responses_test.go index e14c6f0a..0844b462 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/responses_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/responses_test.go @@ -94,3 +94,29 @@ var ListResourceRecordSetsExample = ` testdoc2.example.com NS ` + +var ListHostedZonesExample = ` + + + + /hostedzone/Z2K123214213123 + example.com. + D2224C5B-684A-DB4A-BB9A-E09E3BAFEA7A + + Test comment + + 10 + + + /hostedzone/ZLT12321321124 + sub.example.com. + A970F076-FCB1-D959-B395-96474CC84EB8 + + Test comment for subdomain host + + 4 + + + false + 100 +` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53.go index 3300c249..a18788a9 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53.go @@ -95,6 +95,16 @@ func (r *Route53) query(method, path string, req, resp interface{}) error { if err := enc.EncodeElement(req, start); err != nil { return err } + + // This is really a sadness, but can't think of a better way to + // do this for now in Go's constructs. + replace := "" + if strings.Contains(bodyBuf.String(), replace) { + var newBuf bytes.Buffer + newBuf.WriteString(strings.Replace(bodyBuf.String(), replace, "", -1)) + bodyBuf = &newBuf + } + body = bodyBuf } @@ -196,6 +206,33 @@ func (r *Route53) GetHostedZone(ID string) (*GetHostedZoneResponse, error) { return out, err } +type ListHostedZonesResponse struct { + HostedZones []HostedZone `xml:"HostedZones>HostedZone"` + Marker string `xml:"Marker"` + IsTruncated bool `xml:"IsTruncated"` + NextMarker string `xml:"NextMarker"` + MaxItems int `xml:"MaxItems"` +} + +func (r *Route53) ListHostedZones(marker string, maxItems int) (*ListHostedZonesResponse, error) { + values := url.Values{} + + if marker != "" { + values.Add("marker", marker) + } + + if maxItems != 0 { + values.Add("maxItems", strconv.Itoa(maxItems)) + } + + out := &ListHostedZonesResponse{} + err := r.query("GET", fmt.Sprintf("/%s/hostedzone/", APIVersion), values, out) + if err != nil { + return nil, err + } + return out, err +} + type GetChangeResponse struct { ChangeInfo ChangeInfo `xml:"ChangeInfo"` } @@ -232,10 +269,29 @@ type ChangeResourceRecordSetsResponse struct { func (r *Route53) ChangeResourceRecordSets(zone string, req *ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) { + // This is really sad, but we have to format this differently + // for Route53 to make them happy. + reqCopy := *req + for i, change := range reqCopy.Changes { + if len(change.Record.Records) > 1 { + var buf bytes.Buffer + for _, r := range change.Record.Records { + buf.WriteString(fmt.Sprintf( + "%s", + r)) + } + + change.Record.Records = nil + change.Record.RecordsXML = fmt.Sprintf( + "%s", buf.String()) + reqCopy.Changes[i] = change + } + } + zone = CleanZoneID(zone) out := &ChangeResourceRecordSetsResponse{} if err := r.query("POST", fmt.Sprintf("/%s/hostedzone/%s/rrset", APIVersion, - zone), req, out); err != nil { + zone), reqCopy, out); err != nil { return nil, err } return out, nil @@ -261,13 +317,15 @@ type ResourceRecordSet struct { Name string `xml:"Name"` Type string `xml:"Type"` TTL int `xml:"TTL"` - Records []string `xml:"ResourceRecords>ResourceRecord>Value"` + Records []string `xml:"ResourceRecords>ResourceRecord>Value,omitempty"` SetIdentifier string `xml:"SetIdentifier,omitempty"` Weight int `xml:"Weight,omitempty"` HealthCheckId string `xml:"HealthCheckId,omitempty"` Region string `xml:"Region,omitempty"` Failover string `xml:"Failover,omitempty"` AliasTarget *AliasTarget `xml:"AliasTarget,omitempty"` + + RecordsXML string `xml:",innerxml"` } func (r *Route53) ListResourceRecordSets(zone string, lopts *ListOpts) (*ListResourceRecordSetsResponse, error) { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53_test.go index 90316903..092c8174 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/route53/route53_test.go @@ -157,6 +157,25 @@ func TestListResourceRecordSets(t *testing.T) { } } +func TestListHostedZones(t *testing.T) { + testServer := makeTestServer() + client := makeClient(testServer) + testServer.Response(200, nil, ListHostedZonesExample) + + resp, err := client.ListHostedZones("", 0) + if err != nil { + t.Fatalf("err: %v", err) + } + + if resp.HostedZones[0].Name != "example.com." { + t.Fatalf("bad: %v", resp) + } + + if resp.HostedZones[1].Name != "sub.example.com." { + t.Fatalf("bad: %v", resp) + } +} + func decode(t *testing.T, r io.Reader, out interface{}) { var buf1 bytes.Buffer var buf2 bytes.Buffer diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3.go index 989d6783..d05731be 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3.go @@ -619,7 +619,7 @@ type request struct { // amazonShouldEscape returns true if byte should be escaped func amazonShouldEscape(c byte) bool { return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~' || c == '.' || c == '/') + (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~' || c == '.' || c == '/' || c == ':') } // amazonEscape does uri escaping exactly as Amazon does @@ -706,22 +706,6 @@ func (s3 *S3) prepare(req *request) error { req.path = "/" + req.path } req.signpath = req.path - // signpath includes subresource, if present: "?acl", "?delete", "?location", "?logging", or "?torrent" - if req.params["acl"] != nil { - req.signpath += "?acl" - } - if req.params["delete"] != nil { - req.signpath += "?delete" - } - if req.params["location"] != nil { - req.signpath += "?location" - } - if req.params["logging"] != nil { - req.signpath += "?logging" - } - if req.params["torrent"] != nil { - req.signpath += "?torrent" - } if req.bucket != "" { req.baseurl = s3.Region.S3BucketEndpoint diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3_test.go index 12286a03..376c9470 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/s3_test.go @@ -427,3 +427,9 @@ func (s *S) TestGetKey(c *C) { c.Assert(key.Size, Equals, int64(434234)) c.Assert(key.ETag, Equals, GetKeyHeaderDump["ETag"]) } + +func (s *S) TestUnescapedColon(c *C) { + b := s.s3.Bucket("bucket") + u := b.URL("foo:bar") + c.Assert(u, Equals, "http://localhost:4444/bucket/foo:bar") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign.go index 8d2d7985..262503dc 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign.go @@ -4,10 +4,11 @@ import ( "crypto/hmac" "crypto/sha1" "encoding/base64" - "github.com/mitchellh/goamz/aws" "log" "sort" "strings" + + "github.com/mitchellh/goamz/aws" ) var b64 = base64.StdEncoding @@ -17,6 +18,7 @@ var b64 = base64.StdEncoding var s3ParamsToSign = map[string]bool{ "acl": true, + "delete": true, "location": true, "logging": true, "notification": true, @@ -47,6 +49,11 @@ func sign(auth aws.Auth, method, canonicalPath string, params, headers map[strin headers["x-amz-security-token"] = []string{auth.Token} } + if auth.SecretKey == "" { + // no auth secret; skip signing, e.g. for public read-only buckets. + return + } + for k, v := range headers { k = strings.ToLower(k) switch k { @@ -111,6 +118,7 @@ func sign(auth aws.Auth, method, canonicalPath string, params, headers map[strin } else { headers["Authorization"] = []string{"AWS " + auth.AccessKey + ":" + string(signature)} } + if debug { log.Printf("Signature payload: %q", payload) log.Printf("Signature: %q", signature) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign_test.go index 9c19970d..9d9fe8d2 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mitchellh/goamz/s3/sign_test.go @@ -9,6 +9,7 @@ import ( // S3 ReST authentication docs: http://goo.gl/G1LrK var testAuth = aws.Auth{"0PN5J17HBGZHT7JJ3X82", "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o", ""} +var emptyAuth = aws.Auth{"", "", ""} func (s *S) TestSignExampleObjectGet(c *C) { method := "GET" @@ -22,6 +23,17 @@ func (s *S) TestSignExampleObjectGet(c *C) { c.Assert(headers["Authorization"], DeepEquals, []string{expected}) } +func (s *S) TestSignExampleObjectGetNoAuth(c *C) { + method := "GET" + path := "/johnsmith/photos/puppy.jpg" + headers := map[string][]string{ + "Host": {"johnsmith.s3.amazonaws.com"}, + "Date": {"Tue, 27 Mar 2007 19:36:42 +0000"}, + } + s3.Sign(emptyAuth, method, path, nil, headers) + c.Assert(headers["Authorization"], IsNil) +} + func (s *S) TestSignExampleObjectPut(c *C) { method := "PUT" path := "/johnsmith/photos/puppy.jpg" @@ -54,6 +66,23 @@ func (s *S) TestSignExampleList(c *C) { c.Assert(headers["Authorization"], DeepEquals, []string{expected}) } +func (s *S) TestSignExampleListNoAuth(c *C) { + method := "GET" + path := "/johnsmith/" + params := map[string][]string{ + "prefix": {"photos"}, + "max-keys": {"50"}, + "marker": {"puppy"}, + } + headers := map[string][]string{ + "Host": {"johnsmith.s3.amazonaws.com"}, + "Date": {"Tue, 27 Mar 2007 19:42:41 +0000"}, + "User-Agent": {"Mozilla/5.0"}, + } + s3.Sign(emptyAuth, method, path, params, headers) + c.Assert(headers["Authorization"], IsNil) +} + func (s *S) TestSignExampleFetch(c *C) { method := "GET" path := "/johnsmith/" @@ -69,6 +98,20 @@ func (s *S) TestSignExampleFetch(c *C) { c.Assert(headers["Authorization"], DeepEquals, []string{expected}) } +func (s *S) TestSignExampleFetchNoAuth(c *C) { + method := "GET" + path := "/johnsmith/" + params := map[string][]string{ + "acl": {""}, + } + headers := map[string][]string{ + "Host": {"johnsmith.s3.amazonaws.com"}, + "Date": {"Tue, 27 Mar 2007 19:44:46 +0000"}, + } + s3.Sign(emptyAuth, method, path, params, headers) + c.Assert(headers["Authorization"], IsNil) +} + func (s *S) TestSignExampleDelete(c *C) { method := "DELETE" path := "/johnsmith/photos/puppy.jpg" diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/flag/flag.go b/src/github.com/smira/aptly/_vendor/src/github.com/smira/flag/flag.go index 92f4fa2d..04ec27e0 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/smira/flag/flag.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/flag/flag.go @@ -329,6 +329,17 @@ func Lookup(name string) *Flag { return commandLine.formal[name] } +// IsSet checks whether flag has been actually set +func (f *FlagSet) IsSet(name string) bool { + _, exists := f.actual[name] + return exists +} + +// IsSet checks whether flag has been actually set on the command-lien +func IsSet(name string) bool { + return commandLine.IsSet(name) +} + // Set sets the value of the named flag. func (f *FlagSet) Set(name, value string) error { flag, ok := f.formal[name] diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/.gitignore b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/LICENSE new file mode 100644 index 00000000..1924f791 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Andrey Smirnov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/README.md new file mode 100644 index 00000000..4faafae8 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/README.md @@ -0,0 +1,23 @@ +go-ftp-protocol +=============== + +Plugin for http.Transport with support for ftp:// protocol in Go. + +Limitations: only anonymous FTP servers, only file retrieval operations. + +Internally connections to FTP servers are cached and re-used when possible. + +Example usage: + + import "github.com/smira/go-ftp-protocol/protocol" + + transport := &http.Transport{} + transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{}) + + client := &http.Client{Transport: transport} + + resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README") + +License: MIT + +Base on FTP client library: http://github.com/jlaffaye/ftp/ \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol.go b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol.go new file mode 100644 index 00000000..e8721eed --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol.go @@ -0,0 +1,152 @@ +// Package protocol implements ftp:// scheme plugin for http.Transport +// +// github.com/jlaffaye/ftp library is used internally as FTP client implementation. +// +// Limitations: only anonymous FTP servers, only file retrieval operations. +// +// Internally connections to FTP servers are cached and re-used when possible. +// +// Example: +// +// transport := &http.Transport{} +// transport.RegisterProtocol("ftp", &FTPRoundTripper{}) +// client := &http.Client{Transport: transport} +// resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README") +package protocol + +import ( + "fmt" + "github.com/jlaffaye/ftp" + "io" + "net/http" + "net/textproto" + "strings" + "sync" +) + +// FTPRoundTripper is an implementation of net/http.RoundTripper on top of FTP client +type FTPRoundTripper struct { + lock sync.Mutex + idleConnections map[string][]*ftp.ServerConn +} + +// verify interface +var ( + _ http.RoundTripper = &FTPRoundTripper{} +) + +type readCloserWrapper struct { + body io.ReadCloser + rt *FTPRoundTripper + hostport string + conn *ftp.ServerConn +} + +func (w *readCloserWrapper) Read(p []byte) (n int, err error) { + return w.body.Read(p) +} + +func (w *readCloserWrapper) Close() error { + err := w.body.Close() + if err == nil { + w.rt.putConnection(w.hostport, w.conn) + } + + return err +} + +func (rt *FTPRoundTripper) getConnection(hostport string) (conn *ftp.ServerConn, err error) { + rt.lock.Lock() + conns, ok := rt.idleConnections[hostport] + if ok && len(conns) > 0 { + conn = conns[0] + rt.idleConnections[hostport] = conns[1:] + rt.lock.Unlock() + return + } + rt.lock.Unlock() + + conn, err = ftp.Connect(hostport) + if err != nil { + return nil, err + } + + err = conn.Login("anonymous", "anonymous") + if err != nil { + conn.Quit() + return nil, err + } + + return conn, nil +} + +func (rt *FTPRoundTripper) putConnection(hostport string, conn *ftp.ServerConn) { + rt.lock.Lock() + defer rt.lock.Unlock() + + if rt.idleConnections == nil { + rt.idleConnections = make(map[string][]*ftp.ServerConn) + } + + rt.idleConnections[hostport] = append(rt.idleConnections[hostport], conn) +} + +// RoundTrip parses incoming GET "HTTP" request and transforms it into +// commands to ftp client +func (rt *FTPRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + if request.URL.Scheme != "ftp" { + return nil, fmt.Errorf("only ftp protocol is supported, got %s", request.Proto) + } + + if request.Method != "GET" { + return nil, fmt.Errorf("only GET method is supported, got %s", request.Method) + } + + hostport := request.URL.Host + if strings.Index(hostport, ":") == -1 { + hostport = hostport + ":21" + } + + connection, err := rt.getConnection(hostport) + if err != nil { + return nil, err + } + + var body io.ReadCloser + body, err = connection.Retr(request.URL.Path) + + if err != nil { + if te, ok := err.(*textproto.Error); ok { + rt.putConnection(hostport, connection) + + if te.Code == ftp.StatusFileUnavailable { + return &http.Response{ + Status: "404 Not Found", + StatusCode: 404, + Proto: "FTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Request: request, + }, nil + } + } + + return nil, err + } + + return &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "FTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: &readCloserWrapper{ + body: body, + rt: rt, + hostport: hostport, + conn: connection, + }, + ContentLength: -1, + Request: request, + }, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol_test.go new file mode 100644 index 00000000..573dada2 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/smira/go-ftp-protocol/protocol/protocol_test.go @@ -0,0 +1,94 @@ +package protocol + +import ( + "io/ioutil" + "net/http" + "strings" + "testing" +) + +func TestProtocol(t *testing.T) { + transport := &http.Transport{} + transport.RegisterProtocol("ftp", &FTPRoundTripper{}) + + client := &http.Client{Transport: transport} + + resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README") + if err != nil { + t.Fatal(err) + } + + if resp.StatusCode != 200 { + t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode) + } + + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + + err = resp.Body.Close() + if err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") { + t.Fatalf("unexpected content: %s", content) + } + + resp, err = client.Get("ftp://ftp.ru.debian.org/debian/missing") + if err != nil { + t.Fatal(err) + } + + if resp.StatusCode != 404 { + t.Fatalf("resp.StatusCode 404 != %d", resp.StatusCode) + } +} + +func TestConcurrent(t *testing.T) { + transport := &http.Transport{} + transport.RegisterProtocol("ftp", &FTPRoundTripper{}) + + client := &http.Client{Transport: transport} + + const concurrency = 4 + const count = 10 + + done := make(chan struct{}, concurrency) + + for i := 0; i < concurrency; i++ { + go func() { + defer func() { done <- struct{}{} }() + + for j := 0; j < 10; j++ { + resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README") + if err != nil { + t.Fatal(err) + } + + if resp.StatusCode != 200 { + t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode) + } + + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + + err = resp.Body.Close() + if err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") { + t.Fatalf("unexpected content: %s", content) + } + } + }() + } + + for i := 0; i < concurrency; i++ { + <-done + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/.travis.yml b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/.travis.yml new file mode 100644 index 00000000..65d13059 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - 1.2 + - 1.3 + - tip + +script: go test ./... \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/README.md index 3776aad7..f8706652 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/README.md +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/README.md @@ -1,5 +1,7 @@ This is an implementation of the [LevelDB key/value database](http:code.google.com/p/leveldb) in the [Go programming language](http:golang.org). +[![Build Status](https://travis-ci.org/syndtr/goleveldb.png?branch=master)](https://travis-ci.org/syndtr/goleveldb) + Installation ----------- @@ -8,7 +10,7 @@ Installation Requirements ----------- -* Need at least `go1.1` or newer. +* Need at least `go1.2` or newer. Usage ----------- @@ -66,6 +68,17 @@ Iterate over subset of database content: err = iter.Error() ... +Iterate over subset of database content with a particular prefix: + + iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil) + for iter.Next() { + // Use key/value. + ... + } + iter.Release() + err = iter.Error() + ... + Batch writes: batch := new(leveldb.Batch) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/bench_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/bench_test.go index ea6801a8..91b42670 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/bench_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/bench_test.go @@ -170,7 +170,7 @@ func (p *dbBench) writes(perBatch int) { b.SetBytes(116) } -func (p *dbBench) drop() { +func (p *dbBench) gc() { p.keys, p.values = nil, nil runtime.GC() } @@ -249,6 +249,9 @@ func (p *dbBench) newIter() iterator.Iterator { } func (p *dbBench) close() { + if bp, err := p.db.GetProperty("leveldb.blockpool"); err == nil { + p.b.Log("Block pool stats: ", bp) + } p.db.Close() p.stor.Close() os.RemoveAll(benchDB) @@ -331,7 +334,7 @@ func BenchmarkDBRead(b *testing.B) { p := openDBBench(b, false) p.populate(b.N) p.fill() - p.drop() + p.gc() iter := p.newIter() b.ResetTimer() @@ -362,7 +365,7 @@ func BenchmarkDBReadUncompressed(b *testing.B) { p := openDBBench(b, true) p.populate(b.N) p.fill() - p.drop() + p.gc() iter := p.newIter() b.ResetTimer() @@ -379,7 +382,7 @@ func BenchmarkDBReadTable(b *testing.B) { p.populate(b.N) p.fill() p.reopen() - p.drop() + p.gc() iter := p.newIter() b.ResetTimer() @@ -395,7 +398,7 @@ func BenchmarkDBReadReverse(b *testing.B) { p := openDBBench(b, false) p.populate(b.N) p.fill() - p.drop() + p.gc() iter := p.newIter() b.ResetTimer() @@ -413,7 +416,7 @@ func BenchmarkDBReadReverseTable(b *testing.B) { p.populate(b.N) p.fill() p.reopen() - p.drop() + p.gc() iter := p.newIter() b.ResetTimer() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache.go index 9b6a7497..baced771 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache.go @@ -11,115 +11,149 @@ import ( "sync/atomic" ) -// SetFunc used by Namespace.Get method to create a cache object. SetFunc -// may return ok false, in that case the cache object will not be created. -type SetFunc func() (ok bool, value interface{}, charge int, fin SetFin) +// SetFunc is the function that will be called by Namespace.Get to create +// a cache object, if charge is less than one than the cache object will +// not be registered to cache tree, if value is nil then the cache object +// will not be created. +type SetFunc func() (charge int, value interface{}) -// SetFin will be called when corresponding cache object are released. -type SetFin func() +// DelFin is the function that will be called as the result of a delete operation. +// Exist == true is indication that the object is exist, and pending == true is +// indication of deletion already happen but haven't done yet (wait for all handles +// to be released). And exist == false means the object doesn't exist. +type DelFin func(exist, pending bool) -// DelFin will be called when corresponding cache object are released. -// DelFin will be called after SetFin. The exist is true if the corresponding -// cache object is actually exist in the cache tree. -type DelFin func(exist bool) +// PurgeFin is the function that will be called as the result of a purge operation. +type PurgeFin func(ns, key uint64) -// PurgeFin will be called when corresponding cache object are released. -// PurgeFin will be called after SetFin. If PurgeFin present DelFin will -// not be executed but passed to the PurgeFin, it is up to the caller -// to call it or not. -type PurgeFin func(ns, key uint64, delfin DelFin) - -// Cache is a cache tree. +// Cache is a cache tree. A cache instance must be goroutine-safe. type Cache interface { - // SetCapacity sets cache capacity. + // SetCapacity sets cache tree capacity. SetCapacity(capacity int) - // GetNamespace gets or creates a cache namespace for the given id. + // Capacity returns cache tree capacity. + Capacity() int + + // Used returns used cache tree capacity. + Used() int + + // Size returns entire alive cache objects size. + Size() int + + // NumObjects returns number of alive objects. + NumObjects() int + + // GetNamespace gets cache namespace with the given id. + // GetNamespace is never return nil. GetNamespace(id uint64) Namespace - // Purge purges all cache namespaces, read Namespace.Purge method documentation. + // PurgeNamespace purges cache namespace with the given id from this cache tree. + // Also read Namespace.Purge. + PurgeNamespace(id uint64, fin PurgeFin) + + // ZapNamespace detaches cache namespace with the given id from this cache tree. + // Also read Namespace.Zap. + ZapNamespace(id uint64) + + // Purge purges all cache namespace from this cache tree. + // This is behave the same as calling Namespace.Purge method on all cache namespace. Purge(fin PurgeFin) - // Zap zaps all cache namespaces, read Namespace.Zap method documentation. - Zap(closed bool) + // Zap detaches all cache namespace from this cache tree. + // This is behave the same as calling Namespace.Zap method on all cache namespace. + Zap() } -// Namespace is a cache namespace. +// Namespace is a cache namespace. A namespace instance must be goroutine-safe. type Namespace interface { - // Get gets cache object for the given key. The given SetFunc (if not nil) will - // be called if the given key does not exist. - // If the given key does not exist, SetFunc is nil or SetFunc return ok false, Get - // will return ok false. - Get(key uint64, setf SetFunc) (obj Object, ok bool) - - // Get deletes cache object for the given key. If exist the cache object will - // be deleted later when all of its handles have been released (i.e. no one use - // it anymore) and the given DelFin (if not nil) will finally be executed. If - // such cache object does not exist the given DelFin will be executed anyway. + // Get gets cache object with the given key. + // If cache object is not found and setf is not nil, Get will atomically creates + // the cache object by calling setf. Otherwise Get will returns nil. // - // Delete returns true if such cache object exist. + // The returned cache handle should be released after use by calling Release + // method. + Get(key uint64, setf SetFunc) Handle + + // Delete removes cache object with the given key from cache tree. + // A deleted cache object will be released as soon as all of its handles have + // been released. + // Delete only happen once, subsequent delete will consider cache object doesn't + // exist, even if the cache object ins't released yet. + // + // If not nil, fin will be called if the cache object doesn't exist or when + // finally be released. + // + // Delete returns true if such cache object exist and never been deleted. Delete(key uint64, fin DelFin) bool - // Purge deletes all cache objects, read Delete method documentation. + // Purge removes all cache objects within this namespace from cache tree. + // This is the same as doing delete on all cache objects. + // + // If not nil, fin will be called on all cache objects when its finally be + // released. Purge(fin PurgeFin) - // Zap detaches the namespace from the cache tree and delete all its cache - // objects. The cache objects deletion and finalizers execution are happen - // immediately, even if its existing handles haven't yet been released. - // A zapped namespace can't never be filled again. - // If closed is false then the Get function will always call the given SetFunc - // if it is not nil, but resultant of the SetFunc will not be cached. - Zap(closed bool) + // Zap detaches namespace from cache tree and release all its cache objects. + // A zapped namespace can never be filled again. + // Calling Get on zapped namespace will always return nil. + Zap() } -// Object is a cache object. -type Object interface { - // Release releases the cache object. Other methods should not be called - // after the cache object has been released. +// Handle is a cache handle. +type Handle interface { + // Release releases this cache handle. This method can be safely called mutiple + // times. Release() - // Value returns value of the cache object. + // Value returns value of this cache handle. + // Value will returns nil after this cache handle have be released. Value() interface{} } +const ( + DelNotExist = iota + DelExist + DelPendig +) + // Namespace state. type nsState int const ( nsEffective nsState = iota nsZapped - nsClosed ) // Node state. type nodeState int const ( - nodeEffective nodeState = iota + nodeZero nodeState = iota + nodeEffective nodeEvicted - nodeRemoved + nodeDeleted ) -// Fake object. -type fakeObject struct { +// Fake handle. +type fakeHandle struct { value interface{} fin func() once uint32 } -func (o *fakeObject) Value() interface{} { - if atomic.LoadUint32(&o.once) == 0 { - return o.value +func (h *fakeHandle) Value() interface{} { + if atomic.LoadUint32(&h.once) == 0 { + return h.value } return nil } -func (o *fakeObject) Release() { - if !atomic.CompareAndSwapUint32(&o.once, 0, 1) { +func (h *fakeHandle) Release() { + if !atomic.CompareAndSwapUint32(&h.once, 0, 1) { return } - if o.fin != nil { - o.fin() - o.fin = nil + if h.fin != nil { + h.fin() + h.fin = nil } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go index 07a9939b..6207e681 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go @@ -8,14 +8,32 @@ package cache import ( "math/rand" + "runtime" + "sync" + "sync/atomic" "testing" + "time" ) -func set(ns Namespace, key uint64, value interface{}, charge int, fin func()) Object { - obj, _ := ns.Get(key, func() (bool, interface{}, int, SetFin) { - return true, value, charge, fin +type releaserFunc struct { + fn func() + value interface{} +} + +func (r releaserFunc) Release() { + if r.fn != nil { + r.fn() + } +} + +func set(ns Namespace, key uint64, value interface{}, charge int, relf func()) Handle { + return ns.Get(key, func() (int, interface{}) { + if relf != nil { + return charge, releaserFunc{relf, value} + } else { + return charge, value + } }) - return obj } func TestCache_HitMiss(t *testing.T) { @@ -43,29 +61,31 @@ func TestCache_HitMiss(t *testing.T) { setfin++ }).Release() for j, y := range cases { - r, ok := ns.Get(y.key, nil) + h := ns.Get(y.key, nil) if j <= i { // should hit - if !ok { + if h == nil { t.Errorf("case '%d' iteration '%d' is miss", i, j) - } else if r.Value().(string) != y.value { - t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, r.Value().(string), y.value) + } else { + if x := h.Value().(releaserFunc).value.(string); x != y.value { + t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value) + } } } else { // should miss - if ok { - t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, r.Value().(string)) + if h != nil { + t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, h.Value().(releaserFunc).value.(string)) } } - if ok { - r.Release() + if h != nil { + h.Release() } } } for i, x := range cases { finalizerOk := false - ns.Delete(x.key, func(exist bool) { + ns.Delete(x.key, func(exist, pending bool) { finalizerOk = true }) @@ -74,22 +94,24 @@ func TestCache_HitMiss(t *testing.T) { } for j, y := range cases { - r, ok := ns.Get(y.key, nil) + h := ns.Get(y.key, nil) if j > i { // should hit - if !ok { + if h == nil { t.Errorf("case '%d' iteration '%d' is miss", i, j) - } else if r.Value().(string) != y.value { - t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, r.Value().(string), y.value) + } else { + if x := h.Value().(releaserFunc).value.(string); x != y.value { + t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value) + } } } else { // should miss - if ok { - t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, r.Value().(string)) + if h != nil { + t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, h.Value().(releaserFunc).value.(string)) } } - if ok { - r.Release() + if h != nil { + h.Release() } } } @@ -107,42 +129,42 @@ func TestLRUCache_Eviction(t *testing.T) { set(ns, 3, 3, 1, nil).Release() set(ns, 4, 4, 1, nil).Release() set(ns, 5, 5, 1, nil).Release() - if r, ok := ns.Get(2, nil); ok { // 1,3,4,5,2 - r.Release() + if h := ns.Get(2, nil); h != nil { // 1,3,4,5,2 + h.Release() } set(ns, 9, 9, 10, nil).Release() // 5,2,9 - for _, x := range []uint64{9, 2, 5, 1} { - r, ok := ns.Get(x, nil) - if !ok { - t.Errorf("miss for key '%d'", x) + for _, key := range []uint64{9, 2, 5, 1} { + h := ns.Get(key, nil) + if h == nil { + t.Errorf("miss for key '%d'", key) } else { - if r.Value().(int) != int(x) { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int)) + if x := h.Value().(int); x != int(key) { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x) } - r.Release() + h.Release() } } o1.Release() - for _, x := range []uint64{1, 2, 5} { - r, ok := ns.Get(x, nil) - if !ok { - t.Errorf("miss for key '%d'", x) + for _, key := range []uint64{1, 2, 5} { + h := ns.Get(key, nil) + if h == nil { + t.Errorf("miss for key '%d'", key) } else { - if r.Value().(int) != int(x) { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int)) + if x := h.Value().(int); x != int(key) { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x) } - r.Release() + h.Release() } } - for _, x := range []uint64{3, 4, 9} { - r, ok := ns.Get(x, nil) - if ok { - t.Errorf("hit for key '%d'", x) - if r.Value().(int) != int(x) { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int)) + for _, key := range []uint64{3, 4, 9} { + h := ns.Get(key, nil) + if h != nil { + t.Errorf("hit for key '%d'", key) + if x := h.Value().(int); x != int(key) { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x) } - r.Release() + h.Release() } } } @@ -153,16 +175,15 @@ func TestLRUCache_SetGet(t *testing.T) { for i := 0; i < 200; i++ { n := uint64(rand.Intn(99999) % 20) set(ns, n, n, 1, nil).Release() - if p, ok := ns.Get(n, nil); ok { - if p.Value() == nil { + if h := ns.Get(n, nil); h != nil { + if h.Value() == nil { t.Errorf("key '%d' contains nil value", n) } else { - got := p.Value().(uint64) - if got != n { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", n, n, got) + if x := h.Value().(uint64); x != n { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", n, n, x) } } - p.Release() + h.Release() } else { t.Errorf("key '%d' doesn't exist", n) } @@ -176,31 +197,429 @@ func TestLRUCache_Purge(t *testing.T) { o2 := set(ns1, 2, 2, 1, nil) ns1.Purge(nil) set(ns1, 3, 3, 1, nil).Release() - for _, x := range []uint64{1, 2, 3} { - r, ok := ns1.Get(x, nil) - if !ok { - t.Errorf("miss for key '%d'", x) + for _, key := range []uint64{1, 2, 3} { + h := ns1.Get(key, nil) + if h == nil { + t.Errorf("miss for key '%d'", key) } else { - if r.Value().(int) != int(x) { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int)) + if x := h.Value().(int); x != int(key) { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x) } - r.Release() + h.Release() } } o1.Release() o2.Release() - for _, x := range []uint64{1, 2} { - r, ok := ns1.Get(x, nil) - if ok { - t.Errorf("hit for key '%d'", x) - if r.Value().(int) != int(x) { - t.Errorf("invalid value for key '%d' want '%d', got '%d'", x, x, r.Value().(int)) + for _, key := range []uint64{1, 2} { + h := ns1.Get(key, nil) + if h != nil { + t.Errorf("hit for key '%d'", key) + if x := h.Value().(int); x != int(key) { + t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x) } - r.Release() + h.Release() } } } +type testingCacheObjectCounter struct { + created uint + released uint +} + +func (c *testingCacheObjectCounter) createOne() { + c.created++ +} + +func (c *testingCacheObjectCounter) releaseOne() { + c.released++ +} + +type testingCacheObject struct { + t *testing.T + cnt *testingCacheObjectCounter + + ns, key uint64 + + releaseCalled bool +} + +func (x *testingCacheObject) Release() { + if !x.releaseCalled { + x.releaseCalled = true + x.cnt.releaseOne() + } else { + x.t.Errorf("duplicate setfin NS#%d KEY#%s", x.ns, x.key) + } +} + +func TestLRUCache_ConcurrentSetGet(t *testing.T) { + runtime.GOMAXPROCS(runtime.NumCPU()) + + seed := time.Now().UnixNano() + t.Logf("seed=%d", seed) + + const ( + N = 2000000 + M = 4000 + C = 3 + ) + + var set, get uint32 + + wg := &sync.WaitGroup{} + c := NewLRUCache(M / 4) + for ni := uint64(0); ni < C; ni++ { + r0 := rand.New(rand.NewSource(seed + int64(ni))) + r1 := rand.New(rand.NewSource(seed + int64(ni) + 1)) + ns := c.GetNamespace(ni) + + wg.Add(2) + go func(ns Namespace, r *rand.Rand) { + for i := 0; i < N; i++ { + x := uint64(r.Int63n(M)) + o := ns.Get(x, func() (int, interface{}) { + atomic.AddUint32(&set, 1) + return 1, x + }) + if v := o.Value().(uint64); v != x { + t.Errorf("#%d invalid value, got=%d", x, v) + } + o.Release() + } + wg.Done() + }(ns, r0) + go func(ns Namespace, r *rand.Rand) { + for i := 0; i < N; i++ { + x := uint64(r.Int63n(M)) + o := ns.Get(x, nil) + if o != nil { + atomic.AddUint32(&get, 1) + if v := o.Value().(uint64); v != x { + t.Errorf("#%d invalid value, got=%d", x, v) + } + o.Release() + } + } + wg.Done() + }(ns, r1) + } + + wg.Wait() + + t.Logf("set=%d get=%d", set, get) +} + +func TestLRUCache_Finalizer(t *testing.T) { + const ( + capacity = 100 + goroutines = 100 + iterations = 10000 + keymax = 8000 + ) + + cnt := &testingCacheObjectCounter{} + + c := NewLRUCache(capacity) + + type instance struct { + seed int64 + rnd *rand.Rand + nsid uint64 + ns Namespace + effective int + handles []Handle + handlesMap map[uint64]int + + delete bool + purge bool + zap bool + wantDel int + delfinCalled int + delfinCalledAll int + delfinCalledEff int + purgefinCalled int + } + + instanceGet := func(p *instance, key uint64) { + h := p.ns.Get(key, func() (charge int, value interface{}) { + to := &testingCacheObject{ + t: t, cnt: cnt, + ns: p.nsid, + key: key, + } + p.effective++ + cnt.createOne() + return 1, releaserFunc{func() { + to.Release() + p.effective-- + }, to} + }) + p.handles = append(p.handles, h) + p.handlesMap[key] = p.handlesMap[key] + 1 + } + instanceRelease := func(p *instance, i int) { + h := p.handles[i] + key := h.Value().(releaserFunc).value.(*testingCacheObject).key + if n := p.handlesMap[key]; n == 0 { + t.Fatal("key ref == 0") + } else if n > 1 { + p.handlesMap[key] = n - 1 + } else { + delete(p.handlesMap, key) + } + h.Release() + p.handles = append(p.handles[:i], p.handles[i+1:]...) + p.handles[len(p.handles) : len(p.handles)+1][0] = nil + } + + seed := time.Now().UnixNano() + t.Logf("seed=%d", seed) + + instances := make([]*instance, goroutines) + for i := range instances { + p := &instance{} + p.handlesMap = make(map[uint64]int) + p.seed = seed + int64(i) + p.rnd = rand.New(rand.NewSource(p.seed)) + p.nsid = uint64(i) + p.ns = c.GetNamespace(p.nsid) + p.delete = i%6 == 0 + p.purge = i%8 == 0 + p.zap = i%12 == 0 || i%3 == 0 + instances[i] = p + } + + runr := rand.New(rand.NewSource(seed - 1)) + run := func(rnd *rand.Rand, x []*instance, init func(p *instance) bool, fn func(p *instance, i int) bool) { + var ( + rx []*instance + rn []int + ) + if init == nil { + rx = append([]*instance{}, x...) + rn = make([]int, len(x)) + } else { + for _, p := range x { + if init(p) { + rx = append(rx, p) + rn = append(rn, 0) + } + } + } + for len(rx) > 0 { + i := rand.Intn(len(rx)) + if fn(rx[i], rn[i]) { + rn[i]++ + } else { + rx = append(rx[:i], rx[i+1:]...) + rn = append(rn[:i], rn[i+1:]...) + } + } + } + + // Get and release. + run(runr, instances, nil, func(p *instance, i int) bool { + if i < iterations { + if len(p.handles) == 0 || p.rnd.Int()%2 == 0 { + instanceGet(p, uint64(p.rnd.Intn(keymax))) + } else { + instanceRelease(p, p.rnd.Intn(len(p.handles))) + } + return true + } else { + return false + } + }) + + if used, cap := c.Used(), c.Capacity(); used > cap { + t.Errorf("Used > capacity, used=%d cap=%d", used, cap) + } + + // Check effective objects. + for i, p := range instances { + if int(p.effective) < len(p.handlesMap) { + t.Errorf("#%d effective objects < acquired handle, eo=%d ah=%d", i, p.effective, len(p.handlesMap)) + } + } + + if want := int(cnt.created - cnt.released); c.Size() != want { + t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size()) + } + + // First delete. + run(runr, instances, func(p *instance) bool { + p.wantDel = p.effective + return p.delete + }, func(p *instance, i int) bool { + key := uint64(i) + if key < keymax { + _, wantExist := p.handlesMap[key] + gotExist := p.ns.Delete(key, func(exist, pending bool) { + p.delfinCalledAll++ + if exist { + p.delfinCalledEff++ + } + }) + if !gotExist && wantExist { + t.Errorf("delete on NS#%d KEY#%d not found", p.nsid, key) + } + return true + } else { + return false + } + }) + + // Second delete. + run(runr, instances, func(p *instance) bool { + p.delfinCalled = 0 + return p.delete + }, func(p *instance, i int) bool { + key := uint64(i) + if key < keymax { + gotExist := p.ns.Delete(key, func(exist, pending bool) { + if exist && !pending { + t.Errorf("delete fin on NS#%d KEY#%d exist and not pending for deletion", p.nsid, key) + } + p.delfinCalled++ + }) + if gotExist { + t.Errorf("delete on NS#%d KEY#%d found", p.nsid, key) + } + return true + } else { + if p.delfinCalled != keymax { + t.Errorf("(2) #%d not all delete fin called, diff=%d", p.ns, keymax-p.delfinCalled) + } + return false + } + }) + + // Purge. + run(runr, instances, func(p *instance) bool { + return p.purge + }, func(p *instance, i int) bool { + p.ns.Purge(func(ns, key uint64) { + p.purgefinCalled++ + }) + return false + }) + + if want := int(cnt.created - cnt.released); c.Size() != want { + t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size()) + } + + // Release. + run(runr, instances, func(p *instance) bool { + return !p.zap + }, func(p *instance, i int) bool { + if len(p.handles) > 0 { + instanceRelease(p, len(p.handles)-1) + return true + } else { + return false + } + }) + + if want := int(cnt.created - cnt.released); c.Size() != want { + t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size()) + } + + // Zap. + run(runr, instances, func(p *instance) bool { + return p.zap + }, func(p *instance, i int) bool { + p.ns.Zap() + p.handles = nil + p.handlesMap = nil + return false + }) + + if want := int(cnt.created - cnt.released); c.Size() != want { + t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size()) + } + + if notrel, used := int(cnt.created-cnt.released), c.Used(); notrel != used { + t.Errorf("Invalid used value, want=%d got=%d", notrel, used) + } + + c.Purge(nil) + + for _, p := range instances { + if p.delete { + if p.delfinCalledAll != keymax { + t.Errorf("#%d not all delete fin called, purge=%v zap=%v diff=%d", p.nsid, p.purge, p.zap, keymax-p.delfinCalledAll) + } + if p.delfinCalledEff != p.wantDel { + t.Errorf("#%d not all effective delete fin called, diff=%d", p.nsid, p.wantDel-p.delfinCalledEff) + } + if p.purge && p.purgefinCalled > 0 { + t.Errorf("#%d some purge fin called, delete=%v zap=%v n=%d", p.nsid, p.delete, p.zap, p.purgefinCalled) + } + } else { + if p.purge { + if p.purgefinCalled != p.wantDel { + t.Errorf("#%d not all purge fin called, delete=%v zap=%v diff=%d", p.nsid, p.delete, p.zap, p.wantDel-p.purgefinCalled) + } + } + } + } + + if cnt.created != cnt.released { + t.Errorf("Some cache object weren't released, created=%d released=%d", cnt.created, cnt.released) + } +} + +func BenchmarkLRUCache_Set(b *testing.B) { + c := NewLRUCache(0) + ns := c.GetNamespace(0) + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + set(ns, i, "", 1, nil) + } +} + +func BenchmarkLRUCache_Get(b *testing.B) { + c := NewLRUCache(0) + ns := c.GetNamespace(0) + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + set(ns, i, "", 1, nil) + } + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + ns.Get(i, nil) + } +} + +func BenchmarkLRUCache_Get2(b *testing.B) { + c := NewLRUCache(0) + ns := c.GetNamespace(0) + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + set(ns, i, "", 1, nil) + } + b.ResetTimer() + for i := uint64(0); i < uint64(b.N); i++ { + ns.Get(i, func() (charge int, value interface{}) { + return 0, nil + }) + } +} + +func BenchmarkLRUCache_Release(b *testing.B) { + c := NewLRUCache(0) + ns := c.GetNamespace(0) + handles := make([]Handle, b.N) + for i := uint64(0); i < uint64(b.N); i++ { + handles[i] = set(ns, i, "", 1, nil) + } + b.ResetTimer() + for _, h := range handles { + h.Release() + } +} + func BenchmarkLRUCache_SetRelease(b *testing.B) { capacity := b.N / 100 if capacity <= 0 { @@ -210,7 +629,7 @@ func BenchmarkLRUCache_SetRelease(b *testing.B) { ns := c.GetNamespace(0) b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - set(ns, i, nil, 1, nil).Release() + set(ns, i, "", 1, nil).Release() } } @@ -227,10 +646,10 @@ func BenchmarkLRUCache_SetReleaseTwice(b *testing.B) { nb := b.N - na for i := uint64(0); i < uint64(na); i++ { - set(ns, i, nil, 1, nil).Release() + set(ns, i, "", 1, nil).Release() } for i := uint64(0); i < uint64(nb); i++ { - set(ns, i, nil, 1, nil).Release() + set(ns, i, "", 1, nil).Release() } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/empty_cache.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/empty_cache.go deleted file mode 100644 index 1fbf8145..00000000 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/empty_cache.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2013, Suryandaru Triandana -// All rights reserved. -// -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package cache - -import ( - "sync" - "sync/atomic" -) - -type emptyCache struct { - sync.Mutex - table map[uint64]*emptyNS -} - -// NewEmptyCache creates a new initialized empty cache. -func NewEmptyCache() Cache { - return &emptyCache{ - table: make(map[uint64]*emptyNS), - } -} - -func (c *emptyCache) GetNamespace(id uint64) Namespace { - c.Lock() - defer c.Unlock() - - if ns, ok := c.table[id]; ok { - return ns - } - - ns := &emptyNS{ - cache: c, - id: id, - table: make(map[uint64]*emptyNode), - } - c.table[id] = ns - return ns -} - -func (c *emptyCache) Purge(fin PurgeFin) { - c.Lock() - for _, ns := range c.table { - ns.purgeNB(fin) - } - c.Unlock() -} - -func (c *emptyCache) Zap(closed bool) { - c.Lock() - for _, ns := range c.table { - ns.zapNB(closed) - } - c.table = make(map[uint64]*emptyNS) - c.Unlock() -} - -func (*emptyCache) SetCapacity(capacity int) {} - -type emptyNS struct { - cache *emptyCache - id uint64 - table map[uint64]*emptyNode - state nsState -} - -func (ns *emptyNS) Get(key uint64, setf SetFunc) (o Object, ok bool) { - ns.cache.Lock() - - switch ns.state { - case nsZapped: - ns.cache.Unlock() - if setf == nil { - return - } - - var value interface{} - var fin func() - ok, value, _, fin = setf() - if ok { - o = &fakeObject{ - value: value, - fin: fin, - } - } - return - case nsClosed: - ns.cache.Unlock() - return - } - - n, ok := ns.table[key] - if ok { - n.ref++ - } else { - if setf == nil { - ns.cache.Unlock() - return - } - - var value interface{} - var fin func() - ok, value, _, fin = setf() - if !ok { - ns.cache.Unlock() - return - } - - n = &emptyNode{ - ns: ns, - key: key, - value: value, - setfin: fin, - ref: 1, - } - ns.table[key] = n - } - - ns.cache.Unlock() - o = &emptyObject{node: n} - return -} - -func (ns *emptyNS) Delete(key uint64, fin DelFin) bool { - ns.cache.Lock() - - if ns.state != nsEffective { - ns.cache.Unlock() - if fin != nil { - fin(false) - } - return false - } - - n, ok := ns.table[key] - if !ok { - ns.cache.Unlock() - if fin != nil { - fin(false) - } - return false - } - n.delfin = fin - ns.cache.Unlock() - return true -} - -func (ns *emptyNS) purgeNB(fin PurgeFin) { - if ns.state != nsEffective { - return - } - for _, n := range ns.table { - n.purgefin = fin - } -} - -func (ns *emptyNS) Purge(fin PurgeFin) { - ns.cache.Lock() - ns.purgeNB(fin) - ns.cache.Unlock() -} - -func (ns *emptyNS) zapNB(closed bool) { - if ns.state != nsEffective { - return - } - for _, n := range ns.table { - n.execFin() - } - if closed { - ns.state = nsClosed - } else { - ns.state = nsZapped - } - ns.table = nil -} - -func (ns *emptyNS) Zap(closed bool) { - ns.cache.Lock() - ns.zapNB(closed) - delete(ns.cache.table, ns.id) - ns.cache.Unlock() -} - -type emptyNode struct { - ns *emptyNS - key uint64 - value interface{} - ref int - setfin SetFin - delfin DelFin - purgefin PurgeFin -} - -func (n *emptyNode) execFin() { - if n.setfin != nil { - n.setfin() - n.setfin = nil - } - if n.purgefin != nil { - n.purgefin(n.ns.id, n.key, n.delfin) - n.delfin = nil - n.purgefin = nil - } else if n.delfin != nil { - n.delfin(true) - n.delfin = nil - } -} - -func (n *emptyNode) evict() { - n.ns.cache.Lock() - n.ref-- - if n.ref == 0 { - if n.ns.state == nsEffective { - // Remove elem. - delete(n.ns.table, n.key) - // Execute finalizer. - n.execFin() - } - } else if n.ref < 0 { - panic("leveldb/cache: emptyNode: negative node reference") - } - n.ns.cache.Unlock() -} - -type emptyObject struct { - node *emptyNode - once uint32 -} - -func (o *emptyObject) Value() interface{} { - if atomic.LoadUint32(&o.once) == 0 { - return o.node.value - } - return nil -} - -func (o *emptyObject) Release() { - if !atomic.CompareAndSwapUint32(&o.once, 0, 1) { - return - } - o.node.evict() - o.node = nil -} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/lru_cache.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/lru_cache.go index 3c98e076..d56c6eac 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/lru_cache.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/lru_cache.go @@ -9,16 +9,24 @@ package cache import ( "sync" "sync/atomic" + + "github.com/syndtr/goleveldb/leveldb/util" ) +// The LLRB implementation were taken from https://github.com/petar/GoLLRB. +// Which contains the following header: +// +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // lruCache represent a LRU cache state. type lruCache struct { - sync.Mutex - - recent lruNode - table map[uint64]*lruNs - capacity int - size int + mu sync.Mutex + recent lruNode + table map[uint64]*lruNs + capacity int + used, size, alive int } // NewLRUCache creates a new initialized LRU cache with the given capacity. @@ -32,245 +40,415 @@ func NewLRUCache(capacity int) Cache { return c } +func (c *lruCache) Capacity() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.capacity +} + +func (c *lruCache) Used() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.used +} + +func (c *lruCache) Size() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.size +} + +func (c *lruCache) NumObjects() int { + c.mu.Lock() + defer c.mu.Unlock() + return c.alive +} + // SetCapacity set cache capacity. func (c *lruCache) SetCapacity(capacity int) { - c.Lock() + c.mu.Lock() c.capacity = capacity c.evict() - c.Unlock() + c.mu.Unlock() } // GetNamespace return namespace object for given id. func (c *lruCache) GetNamespace(id uint64) Namespace { - c.Lock() - defer c.Unlock() + c.mu.Lock() + defer c.mu.Unlock() - if p, ok := c.table[id]; ok { - return p + if ns, ok := c.table[id]; ok { + return ns } - p := &lruNs{ - lru: c, - id: id, - table: make(map[uint64]*lruNode), + ns := &lruNs{lru: c, id: id} + c.table[id] = ns + return ns +} + +func (c *lruCache) ZapNamespace(id uint64) { + c.mu.Lock() + if ns, exist := c.table[id]; exist { + ns.zapNB() + delete(c.table, id) } - c.table[id] = p - return p + c.mu.Unlock() +} + +func (c *lruCache) PurgeNamespace(id uint64, fin PurgeFin) { + c.mu.Lock() + if ns, exist := c.table[id]; exist { + ns.purgeNB(fin) + } + c.mu.Unlock() } // Purge purge entire cache. func (c *lruCache) Purge(fin PurgeFin) { - c.Lock() + c.mu.Lock() for _, ns := range c.table { ns.purgeNB(fin) } - c.Unlock() + c.mu.Unlock() } -func (c *lruCache) Zap(closed bool) { - c.Lock() +func (c *lruCache) Zap() { + c.mu.Lock() for _, ns := range c.table { - ns.zapNB(closed) + ns.zapNB() } c.table = make(map[uint64]*lruNs) - c.Unlock() + c.mu.Unlock() } func (c *lruCache) evict() { top := &c.recent - for n := c.recent.rPrev; c.size > c.capacity && n != top; { + for n := c.recent.rPrev; c.used > c.capacity && n != top; { + if n.state != nodeEffective { + panic("evicting non effective node") + } n.state = nodeEvicted n.rRemove() - n.evictNB() - c.size -= n.charge + n.derefNB() + c.used -= n.charge n = c.recent.rPrev } } type lruNs struct { - lru *lruCache - id uint64 - table map[uint64]*lruNode - state nsState + lru *lruCache + id uint64 + rbRoot *lruNode + state nsState } -func (ns *lruNs) Get(key uint64, setf SetFunc) (o Object, ok bool) { - lru := ns.lru - lru.Lock() - - switch ns.state { - case nsZapped: - lru.Unlock() - if setf == nil { - return - } - - var value interface{} - var fin func() - ok, value, _, fin = setf() - if ok { - o = &fakeObject{ - value: value, - fin: fin, - } - } - return - case nsClosed: - lru.Unlock() - return +func (ns *lruNs) rbGetOrCreateNode(h *lruNode, key uint64) (hn, n *lruNode) { + if h == nil { + n = &lruNode{ns: ns, key: key} + return n, n } - n, ok := ns.table[key] - if ok { - switch n.state { - case nodeEvicted: - // Insert to recent list. - n.state = nodeEffective - n.ref++ - lru.size += n.charge - lru.evict() - fallthrough - case nodeEffective: - // Bump to front - n.rRemove() - n.rInsert(&lru.recent) + if key < h.key { + hn, n = ns.rbGetOrCreateNode(h.rbLeft, key) + if hn != nil { + h.rbLeft = hn + } else { + return nil, n + } + } else if key > h.key { + hn, n = ns.rbGetOrCreateNode(h.rbRight, key) + if hn != nil { + h.rbRight = hn + } else { + return nil, n } - n.ref++ } else { - if setf == nil { - lru.Unlock() - return - } - - var value interface{} - var charge int - var fin func() - ok, value, charge, fin = setf() - if !ok { - lru.Unlock() - return - } - - n = &lruNode{ - ns: ns, - key: key, - value: value, - charge: charge, - setfin: fin, - ref: 2, - } - ns.table[key] = n - n.rInsert(&lru.recent) - - lru.size += charge - lru.evict() + return nil, h } - lru.Unlock() - o = &lruObject{node: n} - return + if rbIsRed(h.rbRight) && !rbIsRed(h.rbLeft) { + h = rbRotLeft(h) + } + if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) { + h = rbRotRight(h) + } + if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) { + rbFlip(h) + } + return h, n +} + +func (ns *lruNs) getOrCreateNode(key uint64) *lruNode { + hn, n := ns.rbGetOrCreateNode(ns.rbRoot, key) + if hn != nil { + ns.rbRoot = hn + ns.rbRoot.rbBlack = true + } + return n +} + +func (ns *lruNs) rbGetNode(key uint64) *lruNode { + h := ns.rbRoot + for h != nil { + switch { + case key < h.key: + h = h.rbLeft + case key > h.key: + h = h.rbRight + default: + return h + } + } + return nil +} + +func (ns *lruNs) getNode(key uint64) *lruNode { + return ns.rbGetNode(key) +} + +func (ns *lruNs) rbDeleteNode(h *lruNode, key uint64) *lruNode { + if h == nil { + return nil + } + + if key < h.key { + if h.rbLeft == nil { // key not present. Nothing to delete + return h + } + if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) { + h = rbMoveLeft(h) + } + h.rbLeft = ns.rbDeleteNode(h.rbLeft, key) + } else { + if rbIsRed(h.rbLeft) { + h = rbRotRight(h) + } + // If @key equals @h.key and no right children at @h + if h.key == key && h.rbRight == nil { + return nil + } + if h.rbRight != nil && !rbIsRed(h.rbRight) && !rbIsRed(h.rbRight.rbLeft) { + h = rbMoveRight(h) + } + // If @key equals @h.key, and (from above) 'h.Right != nil' + if h.key == key { + var x *lruNode + h.rbRight, x = rbDeleteMin(h.rbRight) + if x == nil { + panic("logic") + } + x.rbLeft, h.rbLeft = h.rbLeft, nil + x.rbRight, h.rbRight = h.rbRight, nil + x.rbBlack = h.rbBlack + h = x + } else { // Else, @key is bigger than @h.key + h.rbRight = ns.rbDeleteNode(h.rbRight, key) + } + } + + return rbFixup(h) +} + +func (ns *lruNs) deleteNode(key uint64) { + ns.rbRoot = ns.rbDeleteNode(ns.rbRoot, key) + if ns.rbRoot != nil { + ns.rbRoot.rbBlack = true + } +} + +func (ns *lruNs) rbIterateNodes(h *lruNode, pivot uint64, iter func(n *lruNode) bool) bool { + if h == nil { + return true + } + if h.key >= pivot { + if !ns.rbIterateNodes(h.rbLeft, pivot, iter) { + return false + } + if !iter(h) { + return false + } + } + return ns.rbIterateNodes(h.rbRight, pivot, iter) +} + +func (ns *lruNs) iterateNodes(iter func(n *lruNode) bool) { + ns.rbIterateNodes(ns.rbRoot, 0, iter) +} + +func (ns *lruNs) Get(key uint64, setf SetFunc) Handle { + ns.lru.mu.Lock() + defer ns.lru.mu.Unlock() + + if ns.state != nsEffective { + return nil + } + + var n *lruNode + if setf == nil { + n = ns.getNode(key) + if n == nil { + return nil + } + } else { + n = ns.getOrCreateNode(key) + } + switch n.state { + case nodeZero: + charge, value := setf() + if value == nil { + ns.deleteNode(key) + return nil + } + if charge < 0 { + charge = 0 + } + + n.value = value + n.charge = charge + n.state = nodeEvicted + + ns.lru.size += charge + ns.lru.alive++ + + fallthrough + case nodeEvicted: + if n.charge == 0 { + break + } + + // Insert to recent list. + n.state = nodeEffective + n.ref++ + ns.lru.used += n.charge + ns.lru.evict() + + fallthrough + case nodeEffective: + // Bump to front. + n.rRemove() + n.rInsert(&ns.lru.recent) + case nodeDeleted: + // Do nothing. + default: + panic("invalid state") + } + n.ref++ + + return &lruHandle{node: n} } func (ns *lruNs) Delete(key uint64, fin DelFin) bool { - lru := ns.lru - lru.Lock() + ns.lru.mu.Lock() + defer ns.lru.mu.Unlock() if ns.state != nsEffective { - lru.Unlock() if fin != nil { - fin(false) + fin(false, false) } return false } - n, ok := ns.table[key] - if !ok { - lru.Unlock() + n := ns.getNode(key) + if n == nil { if fin != nil { - fin(false) + fin(false, false) } return false + } - n.delfin = fin switch n.state { - case nodeRemoved: - lru.Unlock() - return false case nodeEffective: - lru.size -= n.charge + ns.lru.used -= n.charge + n.state = nodeDeleted + n.delfin = fin n.rRemove() - n.evictNB() + n.derefNB() + case nodeEvicted: + n.state = nodeDeleted + n.delfin = fin + case nodeDeleted: + if fin != nil { + fin(true, true) + } + return false + default: + panic("invalid state") } - n.state = nodeRemoved - lru.Unlock() return true } func (ns *lruNs) purgeNB(fin PurgeFin) { - lru := ns.lru - if ns.state != nsEffective { - return - } - - for _, n := range ns.table { - n.purgefin = fin - if n.state == nodeEffective { - lru.size -= n.charge - n.rRemove() - n.evictNB() + if ns.state == nsEffective { + var nodes []*lruNode + ns.iterateNodes(func(n *lruNode) bool { + nodes = append(nodes, n) + return true + }) + for _, n := range nodes { + switch n.state { + case nodeEffective: + ns.lru.used -= n.charge + n.state = nodeDeleted + n.purgefin = fin + n.rRemove() + n.derefNB() + case nodeEvicted: + n.state = nodeDeleted + n.purgefin = fin + case nodeDeleted: + default: + panic("invalid state") + } } - n.state = nodeRemoved } } func (ns *lruNs) Purge(fin PurgeFin) { - ns.lru.Lock() + ns.lru.mu.Lock() ns.purgeNB(fin) - ns.lru.Unlock() + ns.lru.mu.Unlock() } -func (ns *lruNs) zapNB(closed bool) { - lru := ns.lru - if ns.state != nsEffective { - return - } - - if closed { - ns.state = nsClosed - } else { +func (ns *lruNs) zapNB() { + if ns.state == nsEffective { ns.state = nsZapped + + ns.iterateNodes(func(n *lruNode) bool { + if n.state == nodeEffective { + ns.lru.used -= n.charge + n.rRemove() + } + ns.lru.size -= n.charge + n.state = nodeDeleted + n.fin() + + return true + }) + ns.rbRoot = nil } - for _, n := range ns.table { - if n.state == nodeEffective { - lru.size -= n.charge - n.rRemove() - } - n.state = nodeRemoved - n.execFin() - } - ns.table = nil } -func (ns *lruNs) Zap(closed bool) { - ns.lru.Lock() - ns.zapNB(closed) +func (ns *lruNs) Zap() { + ns.lru.mu.Lock() + ns.zapNB() delete(ns.lru.table, ns.id) - ns.lru.Unlock() + ns.lru.mu.Unlock() } type lruNode struct { ns *lruNs - rNext, rPrev *lruNode + rNext, rPrev *lruNode + rbLeft, rbRight *lruNode + rbBlack bool key uint64 value interface{} charge int ref int state nodeState - setfin SetFin delfin DelFin purgefin PurgeFin } @@ -284,7 +462,6 @@ func (n *lruNode) rInsert(at *lruNode) { } func (n *lruNode) rRemove() bool { - // only remove if not already removed if n.rPrev == nil { return false } @@ -297,58 +474,149 @@ func (n *lruNode) rRemove() bool { return true } -func (n *lruNode) execFin() { - if n.setfin != nil { - n.setfin() - n.setfin = nil +func (n *lruNode) fin() { + if r, ok := n.value.(util.Releaser); ok { + r.Release() } if n.purgefin != nil { - n.purgefin(n.ns.id, n.key, n.delfin) - n.delfin = nil + if n.delfin != nil { + panic("conflicting delete and purge fin") + } + n.purgefin(n.ns.id, n.key) n.purgefin = nil } else if n.delfin != nil { - n.delfin(true) + n.delfin(true, false) n.delfin = nil } } -func (n *lruNode) evictNB() { +func (n *lruNode) derefNB() { n.ref-- if n.ref == 0 { if n.ns.state == nsEffective { - // remove elem - delete(n.ns.table, n.key) - // execute finalizer - n.execFin() + // Remove elemement. + n.ns.deleteNode(n.key) + n.ns.lru.size -= n.charge + n.ns.lru.alive-- + n.fin() } + n.value = nil } else if n.ref < 0 { panic("leveldb/cache: lruCache: negative node reference") } } -func (n *lruNode) evict() { - n.ns.lru.Lock() - n.evictNB() - n.ns.lru.Unlock() +func (n *lruNode) deref() { + n.ns.lru.mu.Lock() + n.derefNB() + n.ns.lru.mu.Unlock() } -type lruObject struct { +type lruHandle struct { node *lruNode once uint32 } -func (o *lruObject) Value() interface{} { - if atomic.LoadUint32(&o.once) == 0 { - return o.node.value +func (h *lruHandle) Value() interface{} { + if atomic.LoadUint32(&h.once) == 0 { + return h.node.value } return nil } -func (o *lruObject) Release() { - if !atomic.CompareAndSwapUint32(&o.once, 0, 1) { +func (h *lruHandle) Release() { + if !atomic.CompareAndSwapUint32(&h.once, 0, 1) { return } - - o.node.evict() - o.node = nil + h.node.deref() + h.node = nil +} + +func rbIsRed(h *lruNode) bool { + if h == nil { + return false + } + return !h.rbBlack +} + +func rbRotLeft(h *lruNode) *lruNode { + x := h.rbRight + if x.rbBlack { + panic("rotating a black link") + } + h.rbRight = x.rbLeft + x.rbLeft = h + x.rbBlack = h.rbBlack + h.rbBlack = false + return x +} + +func rbRotRight(h *lruNode) *lruNode { + x := h.rbLeft + if x.rbBlack { + panic("rotating a black link") + } + h.rbLeft = x.rbRight + x.rbRight = h + x.rbBlack = h.rbBlack + h.rbBlack = false + return x +} + +func rbFlip(h *lruNode) { + h.rbBlack = !h.rbBlack + h.rbLeft.rbBlack = !h.rbLeft.rbBlack + h.rbRight.rbBlack = !h.rbRight.rbBlack +} + +func rbMoveLeft(h *lruNode) *lruNode { + rbFlip(h) + if rbIsRed(h.rbRight.rbLeft) { + h.rbRight = rbRotRight(h.rbRight) + h = rbRotLeft(h) + rbFlip(h) + } + return h +} + +func rbMoveRight(h *lruNode) *lruNode { + rbFlip(h) + if rbIsRed(h.rbLeft.rbLeft) { + h = rbRotRight(h) + rbFlip(h) + } + return h +} + +func rbFixup(h *lruNode) *lruNode { + if rbIsRed(h.rbRight) { + h = rbRotLeft(h) + } + + if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) { + h = rbRotRight(h) + } + + if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) { + rbFlip(h) + } + + return h +} + +func rbDeleteMin(h *lruNode) (hn, n *lruNode) { + if h == nil { + return nil, nil + } + if h.rbLeft == nil { + return nil, h + } + + if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) { + h = rbMoveLeft(h) + } + + h.rbLeft, n = rbDeleteMin(h.rbLeft) + + return rbFixup(h), n } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/comparer.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/comparer.go index aed84e00..d33d5e9c 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/comparer.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/comparer.go @@ -9,34 +9,48 @@ package leveldb import "github.com/syndtr/goleveldb/leveldb/comparer" type iComparer struct { - cmp comparer.Comparer + ucmp comparer.Comparer } -func (p *iComparer) Name() string { - return p.cmp.Name() +func (icmp *iComparer) uName() string { + return icmp.ucmp.Name() } -func (p *iComparer) Compare(a, b []byte) int { - ia, ib := iKey(a), iKey(b) - r := p.cmp.Compare(ia.ukey(), ib.ukey()) - if r == 0 { - an, bn := ia.num(), ib.num() - if an > bn { - r = -1 - } else if an < bn { - r = 1 +func (icmp *iComparer) uCompare(a, b []byte) int { + return icmp.ucmp.Compare(a, b) +} + +func (icmp *iComparer) uSeparator(dst, a, b []byte) []byte { + return icmp.ucmp.Separator(dst, a, b) +} + +func (icmp *iComparer) uSuccessor(dst, b []byte) []byte { + return icmp.ucmp.Successor(dst, b) +} + +func (icmp *iComparer) Name() string { + return icmp.uName() +} + +func (icmp *iComparer) Compare(a, b []byte) int { + x := icmp.ucmp.Compare(iKey(a).ukey(), iKey(b).ukey()) + if x == 0 { + if m, n := iKey(a).num(), iKey(b).num(); m > n { + x = -1 + } else if m < n { + x = 1 } } - return r + return x } -func (p *iComparer) Separator(dst, a, b []byte) []byte { +func (icmp *iComparer) Separator(dst, a, b []byte) []byte { ua, ub := iKey(a).ukey(), iKey(b).ukey() - dst = p.cmp.Separator(dst, ua, ub) + dst = icmp.ucmp.Separator(dst, ua, ub) if dst == nil { return nil } - if len(dst) < len(ua) && p.cmp.Compare(ua, dst) < 0 { + if len(dst) < len(ua) && icmp.uCompare(ua, dst) < 0 { dst = append(dst, kMaxNumBytes...) } else { // Did not close possibilities that n maybe longer than len(ub). @@ -45,13 +59,13 @@ func (p *iComparer) Separator(dst, a, b []byte) []byte { return dst } -func (p *iComparer) Successor(dst, b []byte) []byte { +func (icmp *iComparer) Successor(dst, b []byte) []byte { ub := iKey(b).ukey() - dst = p.cmp.Successor(dst, ub) + dst = icmp.ucmp.Successor(dst, ub) if dst == nil { return nil } - if len(dst) < len(ub) && p.cmp.Compare(ub, dst) < 0 { + if len(dst) < len(ub) && icmp.uCompare(ub, dst) < 0 { dst = append(dst, kMaxNumBytes...) } else { // Did not close possibilities that n maybe longer than len(ub). diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go index 3910c593..a036e089 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go @@ -176,6 +176,18 @@ func (h *dbCorruptHarness) removeAll(ft storage.FileType) { } } +func (h *dbCorruptHarness) removeOne(ft storage.FileType) { + ff, err := h.stor.GetFiles(ft) + if err != nil { + h.t.Fatal("get files: ", err) + } + f := ff[rand.Intn(len(ff))] + h.t.Logf("removing file @%d", f.Num()) + if err := f.Remove(); err != nil { + h.t.Error("remove file: ", err) + } +} + func (h *dbCorruptHarness) check(min, max int) { p := &h.dbHarness t := p.t @@ -386,6 +398,33 @@ func TestCorruptDB_UnrelatedKeys(t *testing.T) { h.close() } +func TestCorruptDB_Level0NewerFileHasOlderSeqnum(t *testing.T) { + h := newDbCorruptHarness(t) + + h.put("a", "v1") + h.put("b", "v1") + h.compactMem() + h.put("a", "v2") + h.put("b", "v2") + h.compactMem() + h.put("a", "v3") + h.put("b", "v3") + h.compactMem() + h.put("c", "v0") + h.put("d", "v0") + h.compactMem() + h.compactRangeAt(1, "", "") + h.closeDB() + + h.recover() + h.getVal("a", "v3") + h.getVal("b", "v3") + h.getVal("c", "v0") + h.getVal("d", "v0") + + h.close() +} + func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) { h := newDbCorruptHarness(t) @@ -412,3 +451,22 @@ func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) { h.close() } + +func TestCorruptDB_MissingTableFiles(t *testing.T) { + h := newDbCorruptHarness(t) + + h.put("a", "v1") + h.put("b", "v1") + h.compactMem() + h.put("c", "v2") + h.put("d", "v2") + h.compactMem() + h.put("e", "v3") + h.put("f", "v3") + h.closeDB() + + h.removeOne(storage.TypeTable) + h.openAssert(false) + + h.close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go index fe0de77d..979d0ac4 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go @@ -14,6 +14,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "time" "github.com/syndtr/goleveldb/leveldb/iterator" @@ -25,56 +26,54 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -var ( - ErrNotFound = util.ErrNotFound - ErrSnapshotReleased = errors.New("leveldb: snapshot released") - ErrIterReleased = errors.New("leveldb: iterator released") - ErrClosed = errors.New("leveldb: closed") -) - // DB is a LevelDB database. type DB struct { // Need 64-bit alignment. seq uint64 + // Session. s *session - // MemDB + // MemDB. memMu sync.RWMutex - mem *memdb.DB - frozenMem *memdb.DB + memPool chan *memdb.DB + mem, frozenMem *memDB journal *journal.Writer journalWriter storage.Writer journalFile storage.File frozenJournalFile storage.File frozenSeq uint64 - // Snapshot + // Snapshot. snapsMu sync.Mutex snapsRoot snapshotElement - // Write - writeCh chan *Batch - writeMergedCh chan bool - writeLockCh chan struct{} - writeAckCh chan error - journalCh chan *Batch - journalAckCh chan error + // Stats. + aliveSnaps, aliveIters int32 - // Compaction - compCh chan chan<- struct{} - compMemCh chan chan<- struct{} - compMemAckCh chan struct{} - compReqCh chan *cReq - compErrCh chan error - compErrSetCh chan error - compStats [kNumLevels]cStats + // Write. + writeC chan *Batch + writeMergedC chan bool + writeLockC chan struct{} + writeAckC chan error + journalC chan *Batch + journalAckC chan error - // Close - closeWg sync.WaitGroup - closeCh chan struct{} - closed uint32 - closer io.Closer + // Compaction. + tcompCmdC chan cCmd + tcompPauseC chan chan<- struct{} + tcompTriggerC chan struct{} + mcompCmdC chan cCmd + mcompTriggerC chan struct{} + compErrC chan error + compErrSetC chan error + compStats [kNumLevels]cStats + + // Close. + closeW sync.WaitGroup + closeC chan struct{} + closed uint32 + closer io.Closer } func openDB(s *session) (*DB, error) { @@ -84,42 +83,50 @@ func openDB(s *session) (*DB, error) { s: s, // Initial sequence seq: s.stSeq, + // MemDB + memPool: make(chan *memdb.DB, 1), // Write - writeCh: make(chan *Batch), - writeMergedCh: make(chan bool), - writeLockCh: make(chan struct{}, 1), - writeAckCh: make(chan error), - journalCh: make(chan *Batch), - journalAckCh: make(chan error), + writeC: make(chan *Batch), + writeMergedC: make(chan bool), + writeLockC: make(chan struct{}, 1), + writeAckC: make(chan error), + journalC: make(chan *Batch), + journalAckC: make(chan error), // Compaction - compCh: make(chan chan<- struct{}, 1), - compMemCh: make(chan chan<- struct{}, 1), - compMemAckCh: make(chan struct{}, 1), - compReqCh: make(chan *cReq), - compErrCh: make(chan error), - compErrSetCh: make(chan error), + tcompCmdC: make(chan cCmd), + tcompPauseC: make(chan chan<- struct{}), + tcompTriggerC: make(chan struct{}, 1), + mcompCmdC: make(chan cCmd), + mcompTriggerC: make(chan struct{}, 1), + compErrC: make(chan error), + compErrSetC: make(chan error), // Close - closeCh: make(chan struct{}), + closeC: make(chan struct{}), } db.initSnapshot() - db.compMemAckCh <- struct{}{} if err := db.recoverJournal(); err != nil { return nil, err } // Remove any obsolete files. - if err := db.cleanFiles(); err != nil { + if err := db.checkAndCleanFiles(); err != nil { + // Close journal. + if db.journal != nil { + db.journal.Close() + db.journalWriter.Close() + } return nil, err } // Don't include compaction error goroutine into wait group. go db.compactionError() - db.closeWg.Add(2) - go db.compaction() - go db.writeJournal() - db.wakeCompaction(0) + db.closeW.Add(3) + go db.tCompaction() + go db.mCompaction() + go db.jWriter() + go db.mpoolDrain() s.logf("db@open done T·%v", time.Since(start)) @@ -132,13 +139,14 @@ func openDB(s *session) (*DB, error) { // Also, if ErrorIfExist is true and the DB exist Open will returns // os.ErrExist error. // -// Open will return an error with type of ErrManifest if manifest file -// is missing or corrupted. Missing or corrupted manifest file can be -// recovered with Recover function. +// Open will return an error with type of ErrCorrupted if corruption +// detected in the DB. Corrupted DB can be recovered with Recover +// function. // +// The returned DB instance is goroutine-safe. // The DB must be closed after use, by calling Close method. -func Open(p storage.Storage, o *opt.Options) (db *DB, err error) { - s, err := newSession(p, o) +func Open(stor storage.Storage, o *opt.Options) (db *DB, err error) { + s, err := newSession(stor, o) if err != nil { return } @@ -174,10 +182,11 @@ func Open(p storage.Storage, o *opt.Options) (db *DB, err error) { // OpenFile uses standard file-system backed storage implementation as // desribed in the leveldb/storage package. // -// OpenFile will return an error with type of ErrManifest if manifest file -// is missing or corrupted. Missing or corrupted manifest file can be -// recovered with Recover function. +// OpenFile will return an error with type of ErrCorrupted if corruption +// detected in the DB. Corrupted DB can be recovered with Recover +// function. // +// The returned DB instance is goroutine-safe. // The DB must be closed after use, by calling Close method. func OpenFile(path string, o *opt.Options) (db *DB, err error) { stor, err := storage.OpenFile(path) @@ -198,9 +207,10 @@ func OpenFile(path string, o *opt.Options) (db *DB, err error) { // The DB must already exist or it will returns an error. // Also, Recover will ignore ErrorIfMissing and ErrorIfExist options. // +// The returned DB instance is goroutine-safe. // The DB must be closed after use, by calling Close method. -func Recover(p storage.Storage, o *opt.Options) (db *DB, err error) { - s, err := newSession(p, o) +func Recover(stor storage.Storage, o *opt.Options) (db *DB, err error) { + s, err := newSession(stor, o) if err != nil { return } @@ -226,6 +236,7 @@ func Recover(p storage.Storage, o *opt.Options) (db *DB, err error) { // RecoverFile uses standard file-system backed storage implementation as desribed // in the leveldb/storage package. // +// The returned DB instance is goroutine-safe. // The DB must be closed after use, by calling Close method. func RecoverFile(path string, o *opt.Options) (db *DB, err error) { stor, err := storage.OpenFile(path) @@ -242,16 +253,18 @@ func RecoverFile(path string, o *opt.Options) (db *DB, err error) { } func recoverTable(s *session, o *opt.Options) error { - ff0, err := s.getFiles(storage.TypeTable) + // Get all tables and sort it by file number. + tableFiles_, err := s.getFiles(storage.TypeTable) if err != nil { return err } - ff1 := files(ff0) - ff1.sort() + tableFiles := files(tableFiles_) + tableFiles.sort() var mSeq uint64 var good, corrupted int rec := new(sessionRecord) + bpool := util.NewBufferPool(o.GetBlockSize() + 5) buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) { tmp = s.newTemp() writer, err := tmp.Create() @@ -265,8 +278,9 @@ func recoverTable(s *session, o *opt.Options) error { tmp = nil } }() + + // Copy entries. tw := table.NewWriter(writer, o) - // Copy records. for iter.Next() { key := iter.Key() if validIkey(key) { @@ -298,20 +312,23 @@ func recoverTable(s *session, o *opt.Options) error { return err } defer reader.Close() + // Get file size. size, err := reader.Seek(0, 2) if err != nil { return err } + var tSeq uint64 var tgood, tcorrupted, blockerr int - var min, max []byte - tr := table.NewReader(reader, size, nil, o) + var imin, imax []byte + tr := table.NewReader(reader, size, nil, bpool, o) iter := tr.NewIterator(nil, nil) iter.(iterator.ErrorCallbackSetter).SetErrorCallback(func(err error) { s.logf("table@recovery found error @%d %q", file.Num(), err) blockerr++ }) + // Scan the table. for iter.Next() { key := iter.Key() @@ -324,16 +341,17 @@ func recoverTable(s *session, o *opt.Options) error { if seq > tSeq { tSeq = seq } - if min == nil { - min = append([]byte{}, key...) + if imin == nil { + imin = append([]byte{}, key...) } - max = append(max[:0], key...) + imax = append(imax[:0], key...) } if err := iter.Error(); err != nil { iter.Release() return err } iter.Release() + if tgood > 0 { if tcorrupted > 0 || blockerr > 0 { // Rebuild the table. @@ -354,7 +372,7 @@ func recoverTable(s *session, o *opt.Options) error { mSeq = tSeq } // Add table to level 0. - rec.addTable(0, file.Num(), uint64(size), min, max) + rec.addTable(0, file.Num(), uint64(size), imin, imax) s.logf("table@recovery recovered @%d N·%d C·%d B·%d S·%d Q·%d", file.Num(), tgood, tcorrupted, blockerr, size, tSeq) } else { s.logf("table@recovery unrecoverable @%d C·%d B·%d S·%d", file.Num(), tcorrupted, blockerr, size) @@ -365,42 +383,56 @@ func recoverTable(s *session, o *opt.Options) error { return nil } + // Recover all tables. - if len(ff1) > 0 { - s.logf("table@recovery F·%d", len(ff1)) - s.markFileNum(ff1[len(ff1)-1].Num()) - for _, file := range ff1 { + if len(tableFiles) > 0 { + s.logf("table@recovery F·%d", len(tableFiles)) + + // Mark file number as used. + s.markFileNum(tableFiles[len(tableFiles)-1].Num()) + + for _, file := range tableFiles { if err := recoverTable(file); err != nil { return err } } - s.logf("table@recovery recovered F·%d N·%d C·%d Q·%d", len(ff1), good, corrupted, mSeq) + + s.logf("table@recovery recovered F·%d N·%d C·%d Q·%d", len(tableFiles), good, corrupted, mSeq) } + // Set sequence number. rec.setSeq(mSeq + 1) + // Create new manifest. if err := s.create(); err != nil { return err } + // Commit. return s.commit(rec) } -func (d *DB) recoverJournal() error { - s := d.s - icmp := s.cmp - - ff0, err := s.getFiles(storage.TypeJournal) +func (db *DB) recoverJournal() error { + // Get all tables and sort it by file number. + journalFiles_, err := db.s.getFiles(storage.TypeJournal) if err != nil { return err } - ff1 := files(ff0) - ff1.sort() - ff2 := make([]storage.File, 0, len(ff1)) - for _, file := range ff1 { - if file.Num() >= s.stJournalNum || file.Num() == s.stPrevJournalNum { - s.markFileNum(file.Num()) - ff2 = append(ff2, file) + journalFiles := files(journalFiles_) + journalFiles.sort() + + // Discard older journal. + prev := -1 + for i, file := range journalFiles { + if file.Num() >= db.s.stJournalNum { + if prev >= 0 { + i-- + journalFiles[i] = journalFiles[prev] + } + journalFiles = journalFiles[i:] + break + } else if file.Num() == db.s.stPrevJournalNum { + prev = i } } @@ -408,38 +440,43 @@ func (d *DB) recoverJournal() error { var of storage.File var mem *memdb.DB batch := new(Batch) - cm := newCMem(s) + cm := newCMem(db.s) buf := new(util.Buffer) // Options. - strict := s.o.GetStrict(opt.StrictJournal) - checksum := s.o.GetStrict(opt.StrictJournalChecksum) - writeBuffer := s.o.GetWriteBuffer() + strict := db.s.o.GetStrict(opt.StrictJournal) + checksum := db.s.o.GetStrict(opt.StrictJournalChecksum) + writeBuffer := db.s.o.GetWriteBuffer() recoverJournal := func(file storage.File) error { - s.logf("journal@recovery recovering @%d", file.Num()) + db.logf("journal@recovery recovering @%d", file.Num()) reader, err := file.Open() if err != nil { return err } defer reader.Close() + + // Create/reset journal reader instance. if jr == nil { - jr = journal.NewReader(reader, dropper{s, file}, strict, checksum) + jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum) } else { - jr.Reset(reader, dropper{s, file}, strict, checksum) + jr.Reset(reader, dropper{db.s, file}, strict, checksum) } + + // Flush memdb and remove obsolete journal file. if of != nil { if mem.Len() > 0 { if err := cm.flush(mem, 0); err != nil { return err } } - if err := cm.commit(file.Num(), d.seq); err != nil { + if err := cm.commit(file.Num(), db.seq); err != nil { return err } cm.reset() of.Remove() of = nil } - // Reset memdb. + + // Replay journal to memdb. mem.Reset() for { r, err := jr.Next() @@ -449,12 +486,14 @@ func (d *DB) recoverJournal() error { } return err } + buf.Reset() if _, err := buf.ReadFrom(r); err != nil { - if strict { + if err == io.ErrUnexpectedEOF { + continue + } else { return err } - continue } if err := batch.decode(buf.Bytes()); err != nil { return err @@ -462,28 +501,37 @@ func (d *DB) recoverJournal() error { if err := batch.memReplay(mem); err != nil { return err } - d.seq = batch.seq + uint64(batch.len()) + + // Save sequence number. + db.seq = batch.seq + uint64(batch.len()) + + // Flush it if large enough. if mem.Size() >= writeBuffer { - // Large enough, flush it. if err := cm.flush(mem, 0); err != nil { return err } - // Reset memdb. mem.Reset() } } + of = file return nil } + // Recover all journals. - if len(ff2) > 0 { - s.logf("journal@recovery F·%d", len(ff2)) - mem = memdb.New(icmp, writeBuffer) - for _, file := range ff2 { + if len(journalFiles) > 0 { + db.logf("journal@recovery F·%d", len(journalFiles)) + + // Mark file number as used. + db.s.markFileNum(journalFiles[len(journalFiles)-1].Num()) + + mem = memdb.New(db.s.icmp, writeBuffer) + for _, file := range journalFiles { if err := recoverJournal(file); err != nil { return err } } + // Flush the last journal. if mem.Len() > 0 { if err := cm.flush(mem, 0); err != nil { @@ -491,52 +539,60 @@ func (d *DB) recoverJournal() error { } } } + // Create a new journal. - if _, err := d.newMem(0); err != nil { + if _, err := db.newMem(0); err != nil { return err } + // Commit. - if err := cm.commit(d.journalFile.Num(), d.seq); err != nil { + if err := cm.commit(db.journalFile.Num(), db.seq); err != nil { + // Close journal. + if db.journal != nil { + db.journal.Close() + db.journalWriter.Close() + } return err } - // Remove the last journal. + + // Remove the last obsolete journal file. if of != nil { of.Remove() } + return nil } -func (d *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) { - s := d.s - - ucmp := s.cmp.cmp +func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) { ikey := newIKey(key, seq, tSeek) - em, fm := d.getMems() - for _, m := range [...]*memdb.DB{em, fm} { + em, fm := db.getMems() + for _, m := range [...]*memDB{em, fm} { if m == nil { continue } - mk, mv, me := m.Find(ikey) + defer m.decref() + + mk, mv, me := m.mdb.Find(ikey) if me == nil { ukey, _, t, ok := parseIkey(mk) - if ok && ucmp.Compare(ukey, key) == 0 { + if ok && db.s.icmp.uCompare(ukey, key) == 0 { if t == tDel { return nil, ErrNotFound } - return mv, nil + return append([]byte{}, mv...), nil } } else if me != ErrNotFound { return nil, me } } - v := s.version() + v := db.s.version() value, cSched, err := v.get(ikey, ro) v.release() if cSched { - // Wake compaction. - d.wakeCompaction(0) + // Trigger table compaction. + db.compTrigger(db.tcompTriggerC) } return } @@ -544,15 +600,16 @@ func (d *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err // Get gets the value for the given key. It returns ErrNotFound if the // DB does not contain the key. // -// The caller should not modify the contents of the returned slice, but -// it is safe to modify the contents of the argument after Get returns. -func (d *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { - err = d.ok() +// The returned slice is its own copy, it is safe to modify the contents +// of the returned slice. +// It is safe to modify the contents of the argument after Get returns. +func (db *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { + err = db.ok() if err != nil { return } - return d.get(key, d.getSeq(), ro) + return db.get(key, db.getSeq(), ro) } // NewIterator returns an iterator for the latest snapshot of the @@ -571,14 +628,14 @@ func (d *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { // The iterator must be released after use, by calling Release method. // // Also read Iterator documentation of the leveldb/iterator package. -func (d *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { - if err := d.ok(); err != nil { +func (db *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { + if err := db.ok(); err != nil { return iterator.NewEmptyIterator(err) } - p := d.newSnapshot() - defer p.Release() - return p.NewIterator(slice, ro) + snap := db.newSnapshot() + defer snap.Release() + return snap.NewIterator(slice, ro) } // GetSnapshot returns a latest snapshot of the underlying DB. A snapshot @@ -586,12 +643,12 @@ func (d *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterat // content of snapshot are guaranteed to be consistent. // // The snapshot must be released after use, by calling Release method. -func (d *DB) GetSnapshot() (*Snapshot, error) { - if err := d.ok(); err != nil { +func (db *DB) GetSnapshot() (*Snapshot, error) { + if err := db.ok(); err != nil { return nil, err } - return d.newSnapshot(), nil + return db.newSnapshot(), nil } // GetProperty returns value of the given property name. @@ -603,8 +660,18 @@ func (d *DB) GetSnapshot() (*Snapshot, error) { // Returns statistics of the underlying DB. // leveldb.sstables // Returns sstables list for each level. -func (d *DB) GetProperty(name string) (value string, err error) { - err = d.ok() +// leveldb.blockpool +// Returns block pool stats. +// leveldb.cachedblock +// Returns size of cached block. +// leveldb.openedtables +// Returns number of opened tables. +// leveldb.alivesnaps +// Returns number of alive snapshots. +// leveldb.aliveiters +// Returns number of alive iterators. +func (db *DB) GetProperty(name string) (value string, err error) { + err = db.ok() if err != nil { return } @@ -613,11 +680,9 @@ func (d *DB) GetProperty(name string) (value string, err error) { if !strings.HasPrefix(name, prefix) { return "", errors.New("leveldb: GetProperty: unknown property: " + name) } - p := name[len(prefix):] - s := d.s - v := s.version() + v := db.s.version() defer v.release() switch { @@ -634,22 +699,36 @@ func (d *DB) GetProperty(name string) (value string, err error) { value = "Compactions\n" + " Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)\n" + "-------+------------+---------------+---------------+---------------+---------------\n" - for level, tt := range v.tables { - duration, read, write := d.compStats[level].get() - if len(tt) == 0 && duration == 0 { + for level, tables := range v.tables { + duration, read, write := db.compStats[level].get() + if len(tables) == 0 && duration == 0 { continue } value += fmt.Sprintf(" %3d | %10d | %13.5f | %13.5f | %13.5f | %13.5f\n", - level, len(tt), float64(tt.size())/1048576.0, duration.Seconds(), + level, len(tables), float64(tables.size())/1048576.0, duration.Seconds(), float64(read)/1048576.0, float64(write)/1048576.0) } case p == "sstables": - for level, tt := range v.tables { + for level, tables := range v.tables { value += fmt.Sprintf("--- level %d ---\n", level) - for _, t := range tt { - value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.file.Num(), t.size, t.min, t.max) + for _, t := range tables { + value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.file.Num(), t.size, t.imin, t.imax) } } + case p == "blockpool": + value = fmt.Sprintf("%v", db.s.tops.bpool) + case p == "cachedblock": + if bc := db.s.o.GetBlockCache(); bc != nil { + value = fmt.Sprintf("%d", bc.Size()) + } else { + value = "" + } + case p == "openedtables": + value = fmt.Sprintf("%d", db.s.tops.cache.Size()) + case p == "alivesnaps": + value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveSnaps)) + case p == "aliveiters": + value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters)) default: err = errors.New("leveldb: GetProperty: unknown property: " + name) } @@ -657,29 +736,29 @@ func (d *DB) GetProperty(name string) (value string, err error) { return } -// GetApproximateSizes calculates approximate sizes of the given key ranges. +// SizeOf calculates approximate sizes of the given key ranges. // The length of the returned sizes are equal with the length of the given // ranges. The returned sizes measure storage space usage, so if the user // data compresses by a factor of ten, the returned sizes will be one-tenth // the size of the corresponding user data size. // The results may not include the sizes of recently written data. -func (d *DB) GetApproximateSizes(ranges []util.Range) (Sizes, error) { - if err := d.ok(); err != nil { +func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) { + if err := db.ok(); err != nil { return nil, err } - v := d.s.version() + v := db.s.version() defer v.release() sizes := make(Sizes, 0, len(ranges)) for _, r := range ranges { - min := newIKey(r.Start, kMaxSeq, tSeek) - max := newIKey(r.Limit, kMaxSeq, tSeek) - start, err := v.getApproximateOffset(min) + imin := newIKey(r.Start, kMaxSeq, tSeek) + imax := newIKey(r.Limit, kMaxSeq, tSeek) + start, err := v.offsetOf(imin) if err != nil { return nil, err } - limit, err := v.getApproximateOffset(max) + limit, err := v.offsetOf(imax) if err != nil { return nil, err } @@ -693,105 +772,63 @@ func (d *DB) GetApproximateSizes(ranges []util.Range) (Sizes, error) { return sizes, nil } -// CompactRange compacts the underlying DB for the given key range. -// In particular, deleted and overwritten versions are discarded, -// and the data is rearranged to reduce the cost of operations -// needed to access the data. This operation should typically only -// be invoked by users who understand the underlying implementation. -// -// A nil Range.Start is treated as a key before all keys in the DB. -// And a nil Range.Limit is treated as a key after all keys in the DB. -// Therefore if both is nil then it will compact entire DB. -func (d *DB) CompactRange(r util.Range) error { - err := d.ok() - if err != nil { - return err - } - - cch := make(chan struct{}) - req := &cReq{ - level: -1, - min: r.Start, - max: r.Limit, - cch: cch, - } - - // Push manual compaction request. - select { - case _, _ = <-d.closeCh: - return ErrClosed - case err := <-d.compErrCh: - return err - case d.compReqCh <- req: - } - // Wait for compaction - select { - case _, _ = <-d.closeCh: - return ErrClosed - case <-cch: - } - return nil -} - -// Close closes the DB. This will also releases any outstanding snapshot. +// Close closes the DB. This will also releases any outstanding snapshot and +// abort any in-flight compaction. // // It is not safe to close a DB until all outstanding iterators are released. // It is valid to call Close multiple times. Other methods should not be // called after the DB has been closed. -func (d *DB) Close() error { - if !d.setClosed() { +func (db *DB) Close() error { + if !db.setClosed() { return ErrClosed } - s := d.s start := time.Now() - s.log("db@close closing") + db.log("db@close closing") // Clear the finalizer. - runtime.SetFinalizer(d, nil) + runtime.SetFinalizer(db, nil) // Get compaction error. var err error select { - case err = <-d.compErrCh: + case err = <-db.compErrC: default: } - close(d.closeCh) + close(db.closeC) - // wait for the WaitGroup - d.closeWg.Wait() + // Wait for the close WaitGroup. + db.closeW.Wait() - // close journal - if d.journal != nil { - d.journal.Close() - d.journalWriter.Close() + // Close journal. + db.writeLockC <- struct{}{} + if db.journal != nil { + db.journal.Close() + db.journalWriter.Close() } - // close session - s.close() - s.logf("db@close done T·%v", time.Since(start)) - s.release() + // Close session. + db.s.close() + db.logf("db@close done T·%v", time.Since(start)) + db.s.release() - if d.closer != nil { - if err1 := d.closer.Close(); err == nil { + if db.closer != nil { + if err1 := db.closer.Close(); err == nil { err = err1 } } - d.s = nil - d.mem = nil - d.frozenMem = nil - d.journal = nil - d.journalWriter = nil - d.journalFile = nil - d.frozenJournalFile = nil - d.snapsRoot = snapshotElement{} - d.closer = nil - - close(d.writeCh) - close(d.journalCh) - close(d.compReqCh) + // NIL'ing pointers. + db.s = nil + db.mem = nil + db.frozenMem = nil + db.journal = nil + db.journalWriter = nil + db.journalFile = nil + db.frozenJournalFile = nil + db.snapsRoot = snapshotElement{} + db.closer = nil return err } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go index 9e7055ae..4c903208 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go @@ -15,7 +15,7 @@ import ( ) var ( - errTransactExiting = errors.New("leveldb: transact exiting") + errCompactionTransactExiting = errors.New("leveldb: compaction transact exiting") ) type cStats struct { @@ -61,12 +61,6 @@ func (p *cStatsStaging) stopTimer() { } } -type cReq struct { - level int - min, max iKey - cch chan<- struct{} -} - type cMem struct { s *session level int @@ -80,7 +74,7 @@ func newCMem(s *session) *cMem { func (c *cMem) flush(mem *memdb.DB, level int) error { s := c.s - // Write memdb to table + // Write memdb to table. iter := mem.NewIterator(nil) defer iter.Release() t, n, err := s.tops.createFrom(iter) @@ -88,12 +82,13 @@ func (c *cMem) flush(mem *memdb.DB, level int) error { return err } + // Pick level. if level < 0 { - level = s.version_NB().pickLevel(t.min.ukey(), t.max.ukey()) + level = s.version_NB().pickLevel(t.imin.ukey(), t.imax.ukey()) } c.rec.addTableFile(level, t) - s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.min, t.max) + s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax) c.level = level return nil @@ -106,95 +101,152 @@ func (c *cMem) reset() { func (c *cMem) commit(journal, seq uint64) error { c.rec.setJournalNum(journal) c.rec.setSeq(seq) - // Commit changes + + // Commit changes. return c.s.commit(c.rec) } -func (d *DB) compactionError() { +func (db *DB) compactionError() { var err error noerr: for { select { - case _, _ = <-d.closeCh: - return - case err = <-d.compErrSetCh: + case err = <-db.compErrSetC: if err != nil { goto haserr } + case _, _ = <-db.closeC: + return } } haserr: for { select { - case _, _ = <-d.closeCh: - return - case err = <-d.compErrSetCh: + case db.compErrC <- err: + case err = <-db.compErrSetC: if err == nil { goto noerr } - case d.compErrCh <- err: + case _, _ = <-db.closeC: + return } } } -func (d *DB) transact(name string, exec, rollback func() error) { - s := d.s +type compactionTransactCounter int + +func (cnt *compactionTransactCounter) incr() { + *cnt++ +} + +func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactCounter) error, rollback func() error) { defer func() { if x := recover(); x != nil { - if x == errTransactExiting && rollback != nil { + if x == errCompactionTransactExiting && rollback != nil { if err := rollback(); err != nil { - s.logf("%s rollback error %q", name, err) + db.logf("%s rollback error %q", name, err) } } panic(x) } }() + + const ( + backoffMin = 1 * time.Second + backoffMax = 8 * time.Second + backoffMul = 2 * time.Second + ) + backoff := backoffMin + backoffT := time.NewTimer(backoff) + lastCnt := compactionTransactCounter(0) for n := 0; ; n++ { - if d.isClosed() { - s.logf("%s exiting", name) - panic(errTransactExiting) + // Check wether the DB is closed. + if db.isClosed() { + db.logf("%s exiting", name) + db.compactionExitTransact() } else if n > 0 { - s.logf("%s retrying N·%d", name, n) + db.logf("%s retrying N·%d", name, n) } - err := exec() + + // Execute. + cnt := compactionTransactCounter(0) + err := exec(&cnt) + + // Set compaction error status. select { - case _, _ = <-d.closeCh: - s.logf("%s exiting", name) - panic(errTransactExiting) - case d.compErrSetCh <- err: + case db.compErrSetC <- err: + case _, _ = <-db.closeC: + db.logf("%s exiting", name) + db.compactionExitTransact() } if err == nil { return } - s.logf("%s error %q", name, err) - time.Sleep(time.Second) + db.logf("%s error I·%d %q", name, cnt, err) + + // Reset backoff duration if counter is advancing. + if cnt > lastCnt { + backoff = backoffMin + lastCnt = cnt + } + + // Backoff. + backoffT.Reset(backoff) + if backoff < backoffMax { + backoff *= backoffMul + if backoff > backoffMax { + backoff = backoffMax + } + } + select { + case <-backoffT.C: + case _, _ = <-db.closeC: + db.logf("%s exiting", name) + db.compactionExitTransact() + } } } -func (d *DB) memCompaction() { - s := d.s - c := newCMem(s) - stats := new(cStatsStaging) - mem := d.getFrozenMem() +func (db *DB) compactionExitTransact() { + panic(errCompactionTransactExiting) +} - s.logf("mem@flush N·%d S·%s", mem.Len(), shortenb(mem.Size())) +func (db *DB) memCompaction() { + mem := db.getFrozenMem() + if mem == nil { + return + } + defer mem.decref() + + c := newCMem(db.s) + stats := new(cStatsStaging) + + db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size())) // Don't compact empty memdb. - if mem.Len() == 0 { - s.logf("mem@flush skipping") + if mem.mdb.Len() == 0 { + db.logf("mem@flush skipping") // drop frozen mem - d.dropFrozenMem() + db.dropFrozenMem() return } - d.transact("mem@flush", func() (err error) { + // Pause table compaction. + ch := make(chan struct{}) + select { + case db.tcompPauseC <- (chan<- struct{})(ch): + case _, _ = <-db.closeC: + return + } + + db.compactionTransact("mem@flush", func(cnt *compactionTransactCounter) (err error) { stats.startTimer() defer stats.stopTimer() - return c.flush(mem, -1) + return c.flush(mem.mdb, -1) }, func() error { for _, r := range c.rec.addedTables { - s.logf("mem@flush rollback @%d", r.num) - f := s.getTableFile(r.num) + db.logf("mem@flush rollback @%d", r.num) + f := db.s.getTableFile(r.num) if err := f.Remove(); err != nil { return err } @@ -202,54 +254,59 @@ func (d *DB) memCompaction() { return nil }) - d.transact("mem@commit", func() (err error) { + db.compactionTransact("mem@commit", func(cnt *compactionTransactCounter) (err error) { stats.startTimer() defer stats.stopTimer() - return c.commit(d.journalFile.Num(), d.frozenSeq) + return c.commit(db.journalFile.Num(), db.frozenSeq) }, nil) - s.logf("mem@flush commited F·%d T·%v", len(c.rec.addedTables), stats.duration) + db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration) for _, r := range c.rec.addedTables { stats.write += r.size } - d.compStats[c.level].add(stats) + db.compStats[c.level].add(stats) - // drop frozen mem - d.dropFrozenMem() + // Drop frozen mem. + db.dropFrozenMem() - c = nil + // Resume table compaction. + select { + case <-ch: + case _, _ = <-db.closeC: + return + } + + // Trigger table compaction. + db.compTrigger(db.mcompTriggerC) } -func (d *DB) doCompaction(c *compaction, noTrivial bool) { - s := d.s - ucmp := s.cmp.cmp - +func (db *DB) tableCompaction(c *compaction, noTrivial bool) { rec := new(sessionRecord) - rec.addCompactionPointer(c.level, c.max) + rec.addCompactionPointer(c.level, c.imax) if !noTrivial && c.trivial() { t := c.tables[0][0] - s.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1) + db.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1) rec.deleteTable(c.level, t.file.Num()) rec.addTableFile(c.level+1, t) - d.transact("table@move", func() (err error) { - return s.commit(rec) + db.compactionTransact("table@move", func(cnt *compactionTransactCounter) (err error) { + return db.s.commit(rec) }, nil) return } var stats [2]cStatsStaging - for i, tt := range c.tables { - for _, t := range tt { + for i, tables := range c.tables { + for _, t := range tables { stats[i].read += t.size // Insert deleted tables into record rec.deleteTable(c.level+i, t.file.Num()) } } sourceSize := int(stats[0].read + stats[1].read) - minSeq := d.minSeq() - s.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq) + minSeq := db.minSeq() + db.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq) var snapUkey []byte var snapHasUkey bool @@ -257,7 +314,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { var snapIter int var snapDropCnt int var dropCnt int - d.transact("table@build", func() (err error) { + db.compactionTransact("table@build", func(cnt *compactionTransactCounter) (err error) { ukey := append([]byte{}, snapUkey...) hasUkey := snapHasUkey lseq := snapSeq @@ -272,7 +329,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { } rec.addTableFile(c.level+1, t) stats[1].write += t.size - s.logf("table@build created L%d@%d N·%d S·%s %q:%q", c.level+1, t.file.Num(), tw.tw.EntriesLen(), shortenb(int(t.size)), t.min, t.max) + db.logf("table@build created L%d@%d N·%d S·%s %q:%q", c.level+1, t.file.Num(), tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax) return nil } @@ -288,30 +345,17 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { iter := c.newIterator() defer iter.Release() for i := 0; iter.Next(); i++ { + // Incr transact counter. + cnt.incr() + // Skip until last state. if i < snapIter { continue } - // Prioritize memdb compaction. - select { - case _, _ = <-d.closeCh: - err = ErrClosed - return - case cch := <-d.compMemCh: - stats[1].stopTimer() - d.memCompaction() - d.compMemAckCh <- struct{}{} - if cch != nil { - cch <- struct{}{} - } - stats[1].startTimer() - default: - } + ikey := iKey(iter.Key()) - key := iKey(iter.Key()) - - if c.shouldStopBefore(key) && tw != nil { + if c.shouldStopBefore(ikey) && tw != nil { err = finish() if err != nil { return @@ -331,15 +375,15 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { snapSched = false } - if seq, t, ok := key.parseNum(); !ok { + if seq, vt, ok := ikey.parseNum(); !ok { // Don't drop error keys ukey = ukey[:0] hasUkey = false lseq = kMaxSeq } else { - if !hasUkey || ucmp.Compare(key.ukey(), ukey) != 0 { + if !hasUkey || db.s.icmp.uCompare(ikey.ukey(), ukey) != 0 { // First occurrence of this user key - ukey = append(ukey[:0], key.ukey()...) + ukey = append(ukey[:0], ikey.ukey()...) hasUkey = true lseq = kMaxSeq } @@ -348,7 +392,7 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { if lseq <= minSeq { // Dropped because newer entry for same user key exist drop = true // (A) - } else if t == tDel && seq <= minSeq && c.isBaseLevelForKey(ukey) { + } else if vt == tDel && seq <= minSeq && c.baseLevelForKey(ukey) { // For this user key: // (1) there is no data in higher levels // (2) data in lower levels will have larger seq numbers @@ -368,14 +412,24 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { // Create new table if not already if tw == nil { - tw, err = s.tops.create() + // Check for pause event. + select { + case ch := <-db.tcompPauseC: + db.pauseCompaction(ch) + case _, _ = <-db.closeC: + db.compactionExitTransact() + default: + } + + // Create new table. + tw, err = db.s.tops.create() if err != nil { return } } // Write key/value into table - err = tw.add(key, iter.Value()) + err = tw.append(ikey, iter.Value()) if err != nil { return } @@ -407,8 +461,8 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { return }, func() error { for _, r := range rec.addedTables { - s.logf("table@build rollback @%d", r.num) - f := s.getTableFile(r.num) + db.logf("table@build rollback @%d", r.num) + f := db.s.getTableFile(r.num) if err := f.Remove(); err != nil { return err } @@ -417,117 +471,219 @@ func (d *DB) doCompaction(c *compaction, noTrivial bool) { }) // Commit changes - d.transact("table@commit", func() (err error) { + db.compactionTransact("table@commit", func(cnt *compactionTransactCounter) (err error) { stats[1].startTimer() defer stats[1].stopTimer() - return s.commit(rec) + return db.s.commit(rec) }, nil) - resultSize := int(int(stats[1].write)) - s.logf("table@compaction commited F%s S%s D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), dropCnt, stats[1].duration) + resultSize := int(stats[1].write) + db.logf("table@compaction committed F%s S%s D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), dropCnt, stats[1].duration) // Save compaction stats for i := range stats { - d.compStats[c.level+1].add(&stats[i]) + db.compStats[c.level+1].add(&stats[i]) } } -func (d *DB) compaction() { - s := d.s +func (db *DB) tableRangeCompaction(level int, umin, umax []byte) { + db.logf("table@compaction range L%d %q:%q", level, umin, umax) + + if level >= 0 { + if c := db.s.getCompactionRange(level, umin, umax); c != nil { + db.tableCompaction(c, true) + } + } else { + v := db.s.version_NB() + + m := 1 + for i, t := range v.tables[1:] { + if t.overlaps(db.s.icmp, umin, umax, false) { + m = i + 1 + } + } + + for level := 0; level < m; level++ { + if c := db.s.getCompactionRange(level, umin, umax); c != nil { + db.tableCompaction(c, true) + } + } + } +} + +func (db *DB) tableAutoCompaction() { + if c := db.s.pickCompaction(); c != nil { + db.tableCompaction(c, false) + } +} + +func (db *DB) tableNeedCompaction() bool { + return db.s.version_NB().needCompaction() +} + +func (db *DB) pauseCompaction(ch chan<- struct{}) { + select { + case ch <- struct{}{}: + case _, _ = <-db.closeC: + db.compactionExitTransact() + } +} + +type cCmd interface { + ack(err error) +} + +type cIdle struct { + ackC chan<- error +} + +func (r cIdle) ack(err error) { + r.ackC <- err +} + +type cRange struct { + level int + min, max []byte + ackC chan<- error +} + +func (r cRange) ack(err error) { + defer func() { + recover() + }() + if r.ackC != nil { + r.ackC <- err + } +} + +func (db *DB) compSendIdle(compC chan<- cCmd) error { + ch := make(chan error) + defer close(ch) + // Send cmd. + select { + case compC <- cIdle{ch}: + case err := <-db.compErrC: + return err + case _, _ = <-db.closeC: + return ErrClosed + } + // Wait cmd. + return <-ch +} + +func (db *DB) compSendRange(compC chan<- cCmd, level int, min, max []byte) (err error) { + ch := make(chan error) + defer close(ch) + // Send cmd. + select { + case compC <- cRange{level, min, max, ch}: + case err := <-db.compErrC: + return err + case _, _ = <-db.closeC: + return ErrClosed + } + // Wait cmd. + select { + case err = <-db.compErrC: + case err = <-ch: + } + return err +} + +func (db *DB) compTrigger(compTriggerC chan struct{}) { + select { + case compTriggerC <- struct{}{}: + default: + } +} + +func (db *DB) mCompaction() { + var x cCmd + defer func() { if x := recover(); x != nil { - if x != errTransactExiting { + if x != errCompactionTransactExiting { panic(x) } } - d.closeWg.Done() + if x != nil { + x.ack(ErrClosed) + } + db.closeW.Done() }() + for { - var cch chan<- struct{} select { - case _, _ = <-d.closeCh: + case x = <-db.mcompCmdC: + db.memCompaction() + x.ack(nil) + x = nil + case <-db.mcompTriggerC: + db.memCompaction() + case _, _ = <-db.closeC: return - case cch = <-d.compMemCh: - d.memCompaction() - d.compMemAckCh <- struct{}{} - case cch = <-d.compCh: - case creq := <-d.compReqCh: - if creq == nil { - continue - } - s.logf("range compaction L%d %v:%v", creq.level, creq.min, creq.max) - if creq.level >= 0 { - c := s.getCompactionRange(creq.level, creq.min, creq.max) - if c != nil { - d.doCompaction(c, true) - } - } else { - v := s.version_NB() - maxLevel := 1 - for i, tt := range v.tables[1:] { - if tt.isOverlaps(creq.min, creq.max, true, s.cmp) { - maxLevel = i + 1 - } - } - for i := 0; i < maxLevel; i++ { - c := s.getCompactionRange(i, creq.min, creq.max) - if c != nil { - d.doCompaction(c, true) - } - } - } - cch = creq.cch - } - if s.version_NB().needCompaction() { - d.doCompaction(s.pickCompaction(), false) - select { - case d.compCh <- nil: - default: - } - } - if cch != nil { - func() { - defer func() { - recover() - }() - cch <- struct{}{} - }() } } } -func (d *DB) wakeCompaction(wait int) error { - switch wait { - case 0: - select { - case d.compCh <- nil: - default: +func (db *DB) tCompaction() { + var x cCmd + var ackQ []cCmd + + defer func() { + if x := recover(); x != nil { + if x != errCompactionTransactExiting { + panic(x) + } } - case 1: - select { - case _, _ = <-d.closeCh: - return ErrClosed - case err := <-d.compErrCh: - return err - case d.compCh <- nil: + for i := range ackQ { + ackQ[i].ack(ErrClosed) + ackQ[i] = nil } - case 2: - cch := make(chan struct{}) - defer close(cch) - select { - case _, _ = <-d.closeCh: - return ErrClosed - case err := <-d.compErrCh: - return err - case d.compCh <- (chan<- struct{})(cch): + if x != nil { + x.ack(ErrClosed) } - select { - case _, _ = <-d.closeCh: - return ErrClosed - case err := <-d.compErrCh: - return err - case <-cch: + db.closeW.Done() + }() + + for { + if db.tableNeedCompaction() { + select { + case x = <-db.tcompCmdC: + case <-db.tcompTriggerC: + case ch := <-db.tcompPauseC: + db.pauseCompaction(ch) + continue + case _, _ = <-db.closeC: + return + default: + } + } else { + for i := range ackQ { + ackQ[i].ack(nil) + ackQ[i] = nil + } + ackQ = ackQ[:0] + select { + case x = <-db.tcompCmdC: + case <-db.tcompTriggerC: + case ch := <-db.tcompPauseC: + db.pauseCompaction(ch) + continue + case _, _ = <-db.closeC: + return + } } + if x != nil { + switch cmd := x.(type) { + case cIdle: + ackQ = append(ackQ, x) + case cRange: + db.tableRangeCompaction(cmd.level, cmd.min, cmd.max) + x.ack(nil) + } + x = nil + } + db.tableAutoCompaction() } - return nil } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go index 6467291d..49c44059 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go @@ -9,8 +9,9 @@ package leveldb import ( "errors" "runtime" + "sync" + "sync/atomic" - "github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/util" @@ -20,46 +21,61 @@ var ( errInvalidIkey = errors.New("leveldb: Iterator: invalid internal key") ) -func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { - s := db.s +type memdbReleaser struct { + once sync.Once + m *memDB +} +func (mr *memdbReleaser) Release() { + mr.once.Do(func() { + mr.m.decref() + }) +} + +func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { em, fm := db.getMems() - v := s.version() + v := db.s.version() ti := v.getIterators(slice, ro) n := len(ti) + 2 i := make([]iterator.Iterator, 0, n) - i = append(i, em.NewIterator(slice)) + emi := em.mdb.NewIterator(slice) + emi.SetReleaser(&memdbReleaser{m: em}) + i = append(i, emi) if fm != nil { - i = append(i, fm.NewIterator(slice)) + fmi := fm.mdb.NewIterator(slice) + fmi.SetReleaser(&memdbReleaser{m: fm}) + i = append(i, fmi) } i = append(i, ti...) - strict := s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) - mi := iterator.NewMergedIterator(i, s.cmp, strict) + strict := db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) + mi := iterator.NewMergedIterator(i, db.s.icmp, strict) mi.SetReleaser(&versionReleaser{v: v}) return mi } func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *dbIter { - var slice_ *util.Range + var islice *util.Range if slice != nil { - slice_ = &util.Range{} + islice = &util.Range{} if slice.Start != nil { - slice_.Start = newIKey(slice.Start, kMaxSeq, tSeek) + islice.Start = newIKey(slice.Start, kMaxSeq, tSeek) } if slice.Limit != nil { - slice_.Limit = newIKey(slice.Limit, kMaxSeq, tSeek) + islice.Limit = newIKey(slice.Limit, kMaxSeq, tSeek) } } - rawIter := db.newRawIterator(slice_, ro) + rawIter := db.newRawIterator(islice, ro) iter := &dbIter{ - cmp: db.s.cmp.cmp, + db: db, + icmp: db.s.icmp, iter: rawIter, seq: seq, strict: db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator), key: make([]byte, 0), value: make([]byte, 0), } + atomic.AddInt32(&db.aliveIters, 1) runtime.SetFinalizer(iter, (*dbIter).Release) return iter } @@ -76,7 +92,8 @@ const ( // dbIter represent an interator states over a database session. type dbIter struct { - cmp comparer.BasicComparer + db *DB + icmp *iComparer iter iterator.Iterator seq uint64 strict bool @@ -166,7 +183,7 @@ func (i *dbIter) next() bool { i.key = append(i.key[:0], ukey...) i.dir = dirForward case tVal: - if i.dir == dirSOI || i.cmp.Compare(ukey, i.key) > 0 { + if i.dir == dirSOI || i.icmp.uCompare(ukey, i.key) > 0 { i.key = append(i.key[:0], ukey...) i.value = append(i.value[:0], i.iter.Value()...) i.dir = dirForward @@ -211,7 +228,7 @@ func (i *dbIter) prev() bool { ukey, seq, t, ok := parseIkey(i.iter.Key()) if ok { if seq <= i.seq { - if !del && i.cmp.Compare(ukey, i.key) < 0 { + if !del && i.icmp.uCompare(ukey, i.key) < 0 { return true } del = (t == tDel) @@ -252,7 +269,7 @@ func (i *dbIter) Prev() bool { for i.iter.Prev() { ukey, _, _, ok := parseIkey(i.iter.Key()) if ok { - if i.cmp.Compare(ukey, i.key) < 0 { + if i.icmp.uCompare(ukey, i.key) < 0 { goto cont } } else if i.strict { @@ -290,6 +307,7 @@ func (i *dbIter) Release() { if i.releaser != nil { i.releaser.Release() + i.releaser = nil } i.dir = dirReleased @@ -297,13 +315,19 @@ func (i *dbIter) Release() { i.value = nil i.iter.Release() i.iter = nil + atomic.AddInt32(&i.db.aliveIters, -1) + i.db = nil } } func (i *dbIter) SetReleaser(releaser util.Releaser) { - if i.dir != dirReleased { - i.releaser = releaser + if i.dir == dirReleased { + panic(util.ErrReleased) } + if i.releaser != nil && releaser != nil { + panic(util.ErrHasReleaser) + } + i.releaser = releaser } func (i *dbIter) Error() error { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go index 225b7cd5..fb1ce85b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go @@ -9,6 +9,7 @@ package leveldb import ( "runtime" "sync" + "sync/atomic" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" @@ -81,18 +82,19 @@ func (db *DB) minSeq() uint64 { type Snapshot struct { db *DB elem *snapshotElement - mu sync.Mutex + mu sync.RWMutex released bool } // Creates new snapshot object. func (db *DB) newSnapshot() *Snapshot { - p := &Snapshot{ + snap := &Snapshot{ db: db, elem: db.acquireSnapshot(), } - runtime.SetFinalizer(p, (*Snapshot).Release) - return p + atomic.AddInt32(&db.aliveSnaps, 1) + runtime.SetFinalizer(snap, (*Snapshot).Release) + return snap } // Get gets the value for the given key. It returns ErrNotFound if @@ -100,19 +102,18 @@ func (db *DB) newSnapshot() *Snapshot { // // The caller should not modify the contents of the returned slice, but // it is safe to modify the contents of the argument after Get returns. -func (p *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { - db := p.db - err = db.ok() +func (snap *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { + err = snap.db.ok() if err != nil { return } - p.mu.Lock() - defer p.mu.Unlock() - if p.released { + snap.mu.RLock() + defer snap.mu.RUnlock() + if snap.released { err = ErrSnapshotReleased return } - return db.get(key, p.elem.seq, ro) + return snap.db.get(key, snap.elem.seq, ro) } // NewIterator returns an iterator for the snapshot of the uderlying DB. @@ -132,17 +133,18 @@ func (p *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error // iterator would be still valid until released. // // Also read Iterator documentation of the leveldb/iterator package. -func (p *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { - db := p.db - if err := db.ok(); err != nil { +func (snap *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { + if err := snap.db.ok(); err != nil { return iterator.NewEmptyIterator(err) } - p.mu.Lock() - defer p.mu.Unlock() - if p.released { + snap.mu.Lock() + defer snap.mu.Unlock() + if snap.released { return iterator.NewEmptyIterator(ErrSnapshotReleased) } - return db.newIterator(p.elem.seq, slice, ro) + // Since iterator already hold version ref, it doesn't need to + // hold snapshot ref. + return snap.db.newIterator(snap.elem.seq, slice, ro) } // Release releases the snapshot. This will not release any returned @@ -150,16 +152,18 @@ func (p *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator. // underlying DB is closed. // // Other methods should not be called after the snapshot has been released. -func (p *Snapshot) Release() { - p.mu.Lock() - if !p.released { - // Clear the finalizer. - runtime.SetFinalizer(p, nil) +func (snap *Snapshot) Release() { + snap.mu.Lock() + defer snap.mu.Unlock() - p.released = true - p.db.releaseSnapshot(p.elem) - p.db = nil - p.elem = nil + if !snap.released { + // Clear the finalizer. + runtime.SetFinalizer(snap, nil) + + snap.released = true + snap.db.releaseSnapshot(snap.elem) + atomic.AddInt32(&snap.db.aliveSnaps, -1) + snap.db = nil + snap.elem = nil } - p.mu.Unlock() } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_state.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_state.go index d662056f..24ecab50 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_state.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_state.go @@ -8,106 +8,194 @@ package leveldb import ( "sync/atomic" + "time" "github.com/syndtr/goleveldb/leveldb/journal" "github.com/syndtr/goleveldb/leveldb/memdb" ) +type memDB struct { + db *DB + mdb *memdb.DB + ref int32 +} + +func (m *memDB) incref() { + atomic.AddInt32(&m.ref, 1) +} + +func (m *memDB) decref() { + if ref := atomic.AddInt32(&m.ref, -1); ref == 0 { + // Only put back memdb with std capacity. + if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() { + m.mdb.Reset() + m.db.mpoolPut(m.mdb) + } + m.db = nil + m.mdb = nil + } else if ref < 0 { + panic("negative memdb ref") + } +} + // Get latest sequence number. -func (d *DB) getSeq() uint64 { - return atomic.LoadUint64(&d.seq) +func (db *DB) getSeq() uint64 { + return atomic.LoadUint64(&db.seq) } // Atomically adds delta to seq. -func (d *DB) addSeq(delta uint64) { - atomic.AddUint64(&d.seq, delta) +func (db *DB) addSeq(delta uint64) { + atomic.AddUint64(&db.seq, delta) +} + +func (db *DB) mpoolPut(mem *memdb.DB) { + defer func() { + recover() + }() + select { + case db.memPool <- mem: + default: + } +} + +func (db *DB) mpoolGet() *memdb.DB { + select { + case mem := <-db.memPool: + return mem + default: + return nil + } +} + +func (db *DB) mpoolDrain() { + ticker := time.NewTicker(30 * time.Second) + for { + select { + case <-ticker.C: + select { + case <-db.memPool: + default: + } + case _, _ = <-db.closeC: + close(db.memPool) + return + } + } } // Create new memdb and froze the old one; need external synchronization. // newMem only called synchronously by the writer. -func (d *DB) newMem(n int) (mem *memdb.DB, err error) { - s := d.s - - num := s.allocFileNum() - file := s.getJournalFile(num) +func (db *DB) newMem(n int) (mem *memDB, err error) { + num := db.s.allocFileNum() + file := db.s.getJournalFile(num) w, err := file.Create() if err != nil { - s.reuseFileNum(num) + db.s.reuseFileNum(num) return } - d.memMu.Lock() - if d.journal == nil { - d.journal = journal.NewWriter(w) - } else { - d.journal.Reset(w) - d.journalWriter.Close() - d.frozenJournalFile = d.journalFile + + db.memMu.Lock() + defer db.memMu.Unlock() + + if db.frozenMem != nil { + panic("still has frozen mem") } - d.journalWriter = w - d.journalFile = file - d.frozenMem = d.mem - d.mem = memdb.New(s.cmp, maxInt(d.s.o.GetWriteBuffer(), n)) - mem = d.mem - // The seq only incremented by the writer. - d.frozenSeq = d.seq - d.memMu.Unlock() + + if db.journal == nil { + db.journal = journal.NewWriter(w) + } else { + db.journal.Reset(w) + db.journalWriter.Close() + db.frozenJournalFile = db.journalFile + } + db.journalWriter = w + db.journalFile = file + db.frozenMem = db.mem + mdb := db.mpoolGet() + if mdb == nil || mdb.Capacity() < n { + mdb = memdb.New(db.s.icmp, maxInt(db.s.o.GetWriteBuffer(), n)) + } + mem = &memDB{ + db: db, + mdb: mdb, + ref: 2, + } + db.mem = mem + // The seq only incremented by the writer. And whoever called newMem + // should hold write lock, so no need additional synchronization here. + db.frozenSeq = db.seq return } // Get all memdbs. -func (d *DB) getMems() (e *memdb.DB, f *memdb.DB) { - d.memMu.RLock() - defer d.memMu.RUnlock() - return d.mem, d.frozenMem +func (db *DB) getMems() (e, f *memDB) { + db.memMu.RLock() + defer db.memMu.RUnlock() + if db.mem == nil { + panic("nil effective mem") + } + db.mem.incref() + if db.frozenMem != nil { + db.frozenMem.incref() + } + return db.mem, db.frozenMem } // Get frozen memdb. -func (d *DB) getEffectiveMem() *memdb.DB { - d.memMu.RLock() - defer d.memMu.RUnlock() - return d.mem +func (db *DB) getEffectiveMem() *memDB { + db.memMu.RLock() + defer db.memMu.RUnlock() + if db.mem == nil { + panic("nil effective mem") + } + db.mem.incref() + return db.mem } // Check whether we has frozen memdb. -func (d *DB) hasFrozenMem() bool { - d.memMu.RLock() - defer d.memMu.RUnlock() - return d.frozenMem != nil +func (db *DB) hasFrozenMem() bool { + db.memMu.RLock() + defer db.memMu.RUnlock() + return db.frozenMem != nil } // Get frozen memdb. -func (d *DB) getFrozenMem() *memdb.DB { - d.memMu.RLock() - defer d.memMu.RUnlock() - return d.frozenMem +func (db *DB) getFrozenMem() *memDB { + db.memMu.RLock() + defer db.memMu.RUnlock() + if db.frozenMem != nil { + db.frozenMem.incref() + } + return db.frozenMem } // Drop frozen memdb; assume that frozen memdb isn't nil. -func (d *DB) dropFrozenMem() { - d.memMu.Lock() - if err := d.frozenJournalFile.Remove(); err != nil { - d.s.logf("journal@remove removing @%d %q", d.frozenJournalFile.Num(), err) +func (db *DB) dropFrozenMem() { + db.memMu.Lock() + if err := db.frozenJournalFile.Remove(); err != nil { + db.logf("journal@remove removing @%d %q", db.frozenJournalFile.Num(), err) } else { - d.s.logf("journal@remove removed @%d", d.frozenJournalFile.Num()) + db.logf("journal@remove removed @%d", db.frozenJournalFile.Num()) } - d.frozenJournalFile = nil - d.frozenMem = nil - d.memMu.Unlock() + db.frozenJournalFile = nil + db.frozenMem.decref() + db.frozenMem = nil + db.memMu.Unlock() } // Set closed flag; return true if not already closed. -func (d *DB) setClosed() bool { - return atomic.CompareAndSwapUint32(&d.closed, 0, 1) +func (db *DB) setClosed() bool { + return atomic.CompareAndSwapUint32(&db.closed, 0, 1) } // Check whether DB was closed. -func (d *DB) isClosed() bool { - return atomic.LoadUint32(&d.closed) != 0 +func (db *DB) isClosed() bool { + return atomic.LoadUint32(&db.closed) != 0 } // Check read ok status. -func (d *DB) ok() error { - if d.isClosed() { +func (db *DB) ok() error { + if db.isClosed() { return ErrClosed } return nil diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go index 58d1415b..b7190681 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go @@ -124,13 +124,15 @@ func (h *dbHarness) openAssert(want bool) { } } -func (h *dbHarness) put(key, value string) { - t := h.t - db := h.db +func (h *dbHarness) write(batch *Batch) { + if err := h.db.Write(batch, h.wo); err != nil { + h.t.Error("Write: got error: ", err) + } +} - err := db.Put([]byte(key), []byte(value), h.wo) - if err != nil { - t.Error("Put: got error: ", err) +func (h *dbHarness) put(key, value string) { + if err := h.db.Put([]byte(key), []byte(value), h.wo); err != nil { + h.t.Error("Put: got error: ", err) } } @@ -147,15 +149,12 @@ func (h *dbHarness) maxNextLevelOverlappingBytes(want uint64) { db := h.db var res uint64 - ucmp := db.s.cmp.cmp v := db.s.version() for i, tt := range v.tables[1 : len(v.tables)-1] { level := i + 1 next := v.tables[level+1] for _, t := range tt { - var r tFiles - min, max := t.min.ukey(), t.max.ukey() - next.getOverlaps(min, max, &r, true, ucmp) + r := next.getOverlaps(nil, db.s.icmp, t.imin.ukey(), t.imax.ukey(), false) sum := r.size() if sum > res { res = sum @@ -179,6 +178,21 @@ func (h *dbHarness) delete(key string) { } } +func (h *dbHarness) assertNumKeys(want int) { + iter := h.db.NewIterator(nil, h.ro) + defer iter.Release() + got := 0 + for iter.Next() { + got++ + } + if err := iter.Error(); err != nil { + h.t.Error("assertNumKeys: ", err) + } + if want != got { + h.t.Errorf("assertNumKeys: want=%d got=%d", want, got) + } +} + func (h *dbHarness) getr(db Reader, key string, expectFound bool) (found bool, v []byte) { t := h.t v, err := db.Get([]byte(key), h.ro) @@ -221,7 +235,7 @@ func (h *dbHarness) getVal(key, value string) { func (h *dbHarness) allEntriesFor(key, want string) { t := h.t db := h.db - ucmp := db.s.cmp.cmp + s := db.s ikey := newIKey([]byte(key), kMaxSeq, tVal) iter := db.newRawIterator(nil, nil) @@ -234,7 +248,7 @@ func (h *dbHarness) allEntriesFor(key, want string) { for iter.Valid() { rkey := iKey(iter.Key()) if _, t, ok := rkey.parseNum(); ok { - if ucmp.Compare(ikey.ukey(), rkey.ukey()) != 0 { + if s.icmp.uCompare(ikey.ukey(), rkey.ukey()) != 0 { break } if !first { @@ -291,7 +305,16 @@ func (h *dbHarness) getKeyVal(want string) { func (h *dbHarness) waitCompaction() { t := h.t db := h.db - if err := db.wakeCompaction(2); err != nil { + if err := db.compSendIdle(db.tcompCmdC); err != nil { + t.Error("compaction error: ", err) + } +} + +func (h *dbHarness) waitMemCompaction() { + t := h.t + db := h.db + + if err := db.compSendIdle(db.mcompCmdC); err != nil { t.Error("compaction error: ", err) } } @@ -300,41 +323,15 @@ func (h *dbHarness) compactMem() { t := h.t db := h.db - if err := db.wakeCompaction(1); err != nil { + db.writeLockC <- struct{}{} + defer func() { + <-db.writeLockC + }() + + if _, err := db.rotateMem(0); err != nil { t.Error("compaction error: ", err) - return } - - if mem := db.getEffectiveMem(); mem.Len() == 0 { - return - } - - select { - case <-db.compMemAckCh: - case err := <-db.compErrCh: - t.Error("compaction error: ", err) - return - } - - // create new memdb and journal - _, err := db.newMem(0) - if err != nil { - t.Error("newMem: got error: ", err) - return - } - - cch := make(chan struct{}) - // Schedule mem compaction. - select { - case db.compMemCh <- (chan<- struct{})(cch): - case err := <-db.compErrCh: - t.Error("compaction error: ", err) - return - } - // Wait. - select { - case <-cch: - case err := <-db.compErrCh: + if err := db.compSendIdle(db.mcompCmdC); err != nil { t.Error("compaction error: ", err) } @@ -347,35 +344,22 @@ func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool) t := h.t db := h.db - cch := make(chan struct{}) - req := &cReq{level: level, cch: cch} + var _min, _max []byte if min != "" { - req.min = []byte(min) + _min = []byte(min) } if max != "" { - req.max = []byte(max) + _max = []byte(max) } - // Push manual compaction request. - select { - case err := <-db.compErrCh: - t.Error("CompactRangeAt: compaction error: ", err) - return - case db.compReqCh <- req: - } - - // Wait for compaction - select { - case err := <-db.compErrCh: + if err := db.compSendRange(db.tcompCmdC, level, _min, _max); err != nil { if wanterr { t.Log("CompactRangeAt: got error (expected): ", err) } else { t.Error("CompactRangeAt: got error: ", err) } - case <-cch: - if wanterr { - t.Error("CompactRangeAt: expect error") - } + } else if wanterr { + t.Error("CompactRangeAt: expect error") } } @@ -394,8 +378,7 @@ func (h *dbHarness) compactRange(min, max string) { if max != "" { r.Limit = []byte(max) } - err := db.CompactRange(r) - if err != nil { + if err := db.CompactRange(r); err != nil { t.Error("CompactRange: got error: ", err) } } @@ -404,11 +387,11 @@ func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) { t := h.t db := h.db - s, err := db.GetApproximateSizes([]util.Range{ + s, err := db.SizeOf([]util.Range{ {[]byte(start), []byte(limit)}, }) if err != nil { - t.Error("GetApproximateSizes: got error: ", err) + t.Error("SizeOf: got error: ", err) } if s.Sum() < low || s.Sum() > hi { t.Errorf("sizeof %q to %q not in range, want %d - %d, got %d", @@ -1008,6 +991,7 @@ func TestDb_SparseMerge(t *testing.T) { h.put("C", "vc") h.compactMem() h.compactRangeAt(0, "", "") + h.waitCompaction() // Make sparse update h.put("A", "va2") @@ -1017,12 +1001,14 @@ func TestDb_SparseMerge(t *testing.T) { h.maxNextLevelOverlappingBytes(20 * 1048576) h.compactRangeAt(0, "", "") + h.waitCompaction() h.maxNextLevelOverlappingBytes(20 * 1048576) h.compactRangeAt(1, "", "") + h.waitCompaction() h.maxNextLevelOverlappingBytes(20 * 1048576) } -func TestDb_ApproximateSizes(t *testing.T) { +func TestDb_SizeOf(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{ Compression: opt.NoCompression, WriteBuffer: 10000000, @@ -1042,7 +1028,7 @@ func TestDb_ApproximateSizes(t *testing.T) { h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), s1/10)) } - // 0 because GetApproximateSizes() does not account for memtable space + // 0 because SizeOf() does not account for memtable space h.sizeAssert("", numKey(50), 0, 0) for r := 0; r < 3; r++ { @@ -1072,7 +1058,7 @@ func TestDb_ApproximateSizes(t *testing.T) { } } -func TestDb_ApproximateSizes_MixOfSmallAndLarge(t *testing.T) { +func TestDb_SizeOf_MixOfSmallAndLarge(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression}) defer h.close() @@ -1224,7 +1210,7 @@ func TestDb_DeletionMarkers2(t *testing.T) { } func TestDb_CompactionTableOpenError(t *testing.T) { - h := newDbHarnessWopt(t, &opt.Options{MaxOpenFiles: 0}) + h := newDbHarnessWopt(t, &opt.Options{CachedOpenFiles: -1}) defer h.close() im := 10 @@ -1244,7 +1230,7 @@ func TestDb_CompactionTableOpenError(t *testing.T) { h.stor.SetOpenErr(storage.TypeTable) go h.db.CompactRange(util.Range{}) - if err := h.db.wakeCompaction(2); err != nil { + if err := h.db.compSendIdle(h.db.tcompCmdC); err != nil { t.Log("compaction error: ", err) } h.closeDB0() @@ -1486,7 +1472,7 @@ func TestDb_ClosedIsClosed(t *testing.T) { _, err = db.GetProperty("leveldb.stats") assertErr(t, err, true) - _, err = db.GetApproximateSizes([]util.Range{{[]byte("a"), []byte("z")}}) + _, err = db.SizeOf([]util.Range{{[]byte("a"), []byte("z")}}) assertErr(t, err, true) assertErr(t, db.CompactRange(util.Range{}), true) @@ -1591,7 +1577,7 @@ func TestDb_BloomFilter(t *testing.T) { return fmt.Sprintf("key%06d", i) } - n := 10000 + const n = 10000 // Populate multiple layers for i := 0; i < n; i++ { @@ -1828,3 +1814,73 @@ func TestDb_DeletionMarkersOnMemdb(t *testing.T) { h.get("foo", false) h.getKeyVal("") } + +func TestDb_LeveldbIssue178(t *testing.T) { + nKeys := (kMaxTableSize / 30) * 5 + key1 := func(i int) string { + return fmt.Sprintf("my_key_%d", i) + } + key2 := func(i int) string { + return fmt.Sprintf("my_key_%d_xxx", i) + } + + // Disable compression since it affects the creation of layers and the + // code below is trying to test against a very specific scenario. + h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression}) + defer h.close() + + // Create first key range. + batch := new(Batch) + for i := 0; i < nKeys; i++ { + batch.Put([]byte(key1(i)), []byte("value for range 1 key")) + } + h.write(batch) + + // Create second key range. + batch.Reset() + for i := 0; i < nKeys; i++ { + batch.Put([]byte(key2(i)), []byte("value for range 2 key")) + } + h.write(batch) + + // Delete second key range. + batch.Reset() + for i := 0; i < nKeys; i++ { + batch.Delete([]byte(key2(i))) + } + h.write(batch) + h.waitMemCompaction() + + // Run manual compaction. + h.compactRange(key1(0), key1(nKeys-1)) + + // Checking the keys. + h.assertNumKeys(nKeys) +} + +func TestDb_LeveldbIssue200(t *testing.T) { + h := newDbHarness(t) + defer h.close() + + h.put("1", "b") + h.put("2", "c") + h.put("3", "d") + h.put("4", "e") + h.put("5", "f") + + iter := h.db.NewIterator(nil, h.ro) + + // Add an element that should not be reflected in the iterator. + h.put("25", "cd") + + iter.Seek([]byte("5")) + assertBytes(t, []byte("5"), iter.Key()) + iter.Prev() + assertBytes(t, []byte("4"), iter.Key()) + iter.Prev() + assertBytes(t, []byte("3"), iter.Key()) + iter.Next() + assertBytes(t, []byte("4"), iter.Key()) + iter.Next() + assertBytes(t, []byte("5"), iter.Key()) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go index 0fbd66df..4f6b792d 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go @@ -7,6 +7,8 @@ package leveldb import ( + "errors" + "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/storage" @@ -30,45 +32,63 @@ func (p Sizes) Sum() (n uint64) { return n } -// Remove unused files. -func (d *DB) cleanFiles() error { - s := d.s +// Logging. +func (db *DB) log(v ...interface{}) { db.s.log(v...) } +func (db *DB) logf(format string, v ...interface{}) { db.s.logf(format, v...) } - v := s.version_NB() - tables := make(map[uint64]struct{}) - for _, tt := range v.tables { - for _, t := range tt { - tables[t.file.Num()] = struct{}{} +// Check and clean files. +func (db *DB) checkAndCleanFiles() error { + v := db.s.version_NB() + tablesMap := make(map[uint64]bool) + for _, tables := range v.tables { + for _, t := range tables { + tablesMap[t.file.Num()] = false } } - ff, err := s.getFiles(storage.TypeAll) + files, err := db.s.getFiles(storage.TypeAll) if err != nil { return err } + + var nTables int var rem []storage.File - for _, f := range ff { + for _, f := range files { keep := true switch f.Type() { case storage.TypeManifest: - keep = f.Num() >= s.manifestFile.Num() + keep = f.Num() >= db.s.manifestFile.Num() case storage.TypeJournal: - if d.frozenJournalFile != nil { - keep = f.Num() >= d.frozenJournalFile.Num() + if db.frozenJournalFile != nil { + keep = f.Num() >= db.frozenJournalFile.Num() } else { - keep = f.Num() >= d.journalFile.Num() + keep = f.Num() >= db.journalFile.Num() } case storage.TypeTable: - _, keep = tables[f.Num()] + _, keep = tablesMap[f.Num()] + if keep { + tablesMap[f.Num()] = true + nTables++ + } } if !keep { rem = append(rem, f) } } - s.logf("db@janitor F·%d G·%d", len(ff), len(rem)) + + if nTables != len(tablesMap) { + for num, present := range tablesMap { + if !present { + db.logf("db@janitor table missing @%d", num) + } + } + return ErrCorrupted{Type: MissingFiles, Err: errors.New("leveldb: table files missing")} + } + + db.logf("db@janitor F·%d G·%d", len(files), len(rem)) for _, f := range rem { - s.logf("db@janitor removing %s-%d", f.Type(), f.Num()) + db.logf("db@janitor removing %s-%d", f.Type(), f.Num()) if err := f.Remove(); err != nil { return err } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go index 5cd13f43..82725a9e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go @@ -11,48 +11,71 @@ import ( "github.com/syndtr/goleveldb/leveldb/memdb" "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" ) -func (d *DB) doWriteJournal(b *Batch) error { - w, err := d.journal.Next() +func (db *DB) writeJournal(b *Batch) error { + w, err := db.journal.Next() if err != nil { return err } if _, err := w.Write(b.encode()); err != nil { return err } - if err := d.journal.Flush(); err != nil { + if err := db.journal.Flush(); err != nil { return err } if b.sync { - return d.journalWriter.Sync() + return db.journalWriter.Sync() } return nil } -func (d *DB) writeJournal() { - defer d.closeWg.Done() +func (db *DB) jWriter() { + defer db.closeW.Done() for { select { - case _, _ = <-d.closeCh: - return - case b := <-d.journalCh: + case b := <-db.journalC: if b != nil { - d.journalAckCh <- d.doWriteJournal(b) + db.journalAckC <- db.writeJournal(b) } + case _, _ = <-db.closeC: + return } } } -func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) { - s := d.s +func (db *DB) rotateMem(n int) (mem *memDB, err error) { + // Wait for pending memdb compaction. + err = db.compSendIdle(db.mcompCmdC) + if err != nil { + return + } + // Create new memdb and journal. + mem, err = db.newMem(n) + if err != nil { + return + } + + // Schedule memdb compaction. + db.compTrigger(db.mcompTriggerC) + return +} + +func (db *DB) flush(n int) (mem *memDB, nn int, err error) { delayed := false - flush := func() bool { - v := s.version() + flush := func() (retry bool) { + v := db.s.version() defer v.release() - mem = d.getEffectiveMem() - nn = mem.Free() + mem = db.getEffectiveMem() + defer func() { + if retry { + mem.decref() + mem = nil + } + }() + nn = mem.mdb.Free() switch { case v.tLen(0) >= kL0_SlowdownWritesTrigger && !delayed: delayed = true @@ -61,33 +84,23 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) { return false case v.tLen(0) >= kL0_StopWritesTrigger: delayed = true - err = d.wakeCompaction(2) + err = db.compSendIdle(db.tcompCmdC) if err != nil { return false } default: // Allow memdb to grow if it has no entry. - if mem.Len() == 0 { + if mem.mdb.Len() == 0 { nn = n - return false + } else { + mem.decref() + mem, err = db.rotateMem(n) + if err == nil { + nn = mem.mdb.Free() + } else { + nn = 0 + } } - // Wait for pending memdb compaction. - select { - case _, _ = <-d.closeCh: - err = ErrClosed - return false - case <-d.compMemAckCh: - case err = <-d.compErrCh: - return false - } - // Create new memdb and journal. - mem, err = d.newMem(n) - if err != nil { - return false - } - - // Schedule memdb compaction. - d.compMemCh <- nil return false } return true @@ -96,7 +109,7 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) { for flush() { } if delayed { - s.logf("db@write delayed T·%v", time.Since(start)) + db.logf("db@write delayed T·%v", time.Since(start)) } return } @@ -105,8 +118,8 @@ func (d *DB) flush(n int) (mem *memdb.DB, nn int, err error) { // sequentially. // // It is safe to modify the contents of the arguments after Write returns. -func (d *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) { - err = d.ok() +func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) { + err = db.ok() if err != nil || b == nil || b.len() == 0 { return } @@ -116,28 +129,29 @@ func (d *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) { // The write happen synchronously. retry: select { - case _, _ = <-d.closeCh: - return ErrClosed - case d.writeCh <- b: - if <-d.writeMergedCh { - return <-d.writeAckCh + case db.writeC <- b: + if <-db.writeMergedC { + return <-db.writeAckC } goto retry - case d.writeLockCh <- struct{}{}: + case db.writeLockC <- struct{}{}: + case _, _ = <-db.closeC: + return ErrClosed } merged := 0 defer func() { - <-d.writeLockCh + <-db.writeLockC for i := 0; i < merged; i++ { - d.writeAckCh <- err + db.writeAckC <- err } }() - mem, memFree, err := d.flush(b.size()) + mem, memFree, err := db.flush(b.size()) if err != nil { return } + defer mem.decref() // Calculate maximum size of the batch. m := 1 << 20 @@ -150,13 +164,13 @@ retry: drain: for b.size() < m && !b.sync { select { - case nb := <-d.writeCh: + case nb := <-db.writeC: if b.size()+nb.size() <= m { b.append(nb) - d.writeMergedCh <- true + db.writeMergedC <- true merged++ } else { - d.writeMergedCh <- false + db.writeMergedC <- false break drain } default: @@ -165,41 +179,45 @@ drain: } // Set batch first seq number relative from last seq. - b.seq = d.seq + 1 + b.seq = db.seq + 1 // Write journal concurrently if it is large enough. if b.size() >= (128 << 10) { // Push the write batch to the journal writer select { - case _, _ = <-d.closeCh: + case _, _ = <-db.closeC: err = ErrClosed return - case d.journalCh <- b: + case db.journalC <- b: // Write into memdb - b.memReplay(mem) + b.memReplay(mem.mdb) } // Wait for journal writer select { - case _, _ = <-d.closeCh: + case _, _ = <-db.closeC: err = ErrClosed return - case err = <-d.journalAckCh: + case err = <-db.journalAckC: if err != nil { // Revert memdb if error detected - b.revertMemReplay(mem) + b.revertMemReplay(mem.mdb) return } } } else { - err = d.doWriteJournal(b) + err = db.writeJournal(b) if err != nil { return } - b.memReplay(mem) + b.memReplay(mem.mdb) } // Set last seq number. - d.addSeq(uint64(b.len())) + db.addSeq(uint64(b.len())) + + if b.size() >= memFree { + db.rotateMem(0) + } return } @@ -207,18 +225,66 @@ drain: // for that key; a DB is not a multi-map. // // It is safe to modify the contents of the arguments after Put returns. -func (d *DB) Put(key, value []byte, wo *opt.WriteOptions) error { +func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error { b := new(Batch) b.Put(key, value) - return d.Write(b, wo) + return db.Write(b, wo) } // Delete deletes the value for the given key. It returns ErrNotFound if // the DB does not contain the key. // // It is safe to modify the contents of the arguments after Delete returns. -func (d *DB) Delete(key []byte, wo *opt.WriteOptions) error { +func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error { b := new(Batch) b.Delete(key) - return d.Write(b, wo) + return db.Write(b, wo) +} + +func isMemOverlaps(icmp *iComparer, mem *memdb.DB, min, max []byte) bool { + iter := mem.NewIterator(nil) + defer iter.Release() + return (max == nil || (iter.First() && icmp.uCompare(max, iKey(iter.Key()).ukey()) >= 0)) && + (min == nil || (iter.Last() && icmp.uCompare(min, iKey(iter.Key()).ukey()) <= 0)) +} + +// CompactRange compacts the underlying DB for the given key range. +// In particular, deleted and overwritten versions are discarded, +// and the data is rearranged to reduce the cost of operations +// needed to access the data. This operation should typically only +// be invoked by users who understand the underlying implementation. +// +// A nil Range.Start is treated as a key before all keys in the DB. +// And a nil Range.Limit is treated as a key after all keys in the DB. +// Therefore if both is nil then it will compact entire DB. +func (db *DB) CompactRange(r util.Range) error { + if err := db.ok(); err != nil { + return err + } + + select { + case db.writeLockC <- struct{}{}: + case _, _ = <-db.closeC: + return ErrClosed + } + + // Check for overlaps in memdb. + mem := db.getEffectiveMem() + defer mem.decref() + if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) { + // Memdb compaction. + if _, err := db.rotateMem(0); err != nil { + <-db.writeLockC + return err + } + <-db.writeLockC + if err := db.compSendIdle(db.mcompCmdC); err != nil { + return err + } + } else { + <-db.writeLockC + } + + // Table compaction. + return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/doc.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/doc.go index ac9ea3d0..53f13bb2 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/doc.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/doc.go @@ -37,6 +37,16 @@ // err = iter.Error() // ... // +// Iterate over subset of database content with a particular prefix: +// iter := db.NewIterator(util.BytesPrefix([]byte("foo-")), nil) +// for iter.Next() { +// // Use key/value. +// ... +// } +// iter.Release() +// err = iter.Error() +// ... +// // Seek-then-Iterate: // // iter := db.NewIterator(nil, nil) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go new file mode 100644 index 00000000..8066bd9a --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go @@ -0,0 +1,38 @@ +// Copyright (c) 2014, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package leveldb + +import ( + "errors" + + "github.com/syndtr/goleveldb/leveldb/util" +) + +var ( + ErrNotFound = util.ErrNotFound + ErrSnapshotReleased = errors.New("leveldb: snapshot released") + ErrIterReleased = errors.New("leveldb: iterator released") + ErrClosed = errors.New("leveldb: closed") +) + +type CorruptionType int + +const ( + CorruptedManifest CorruptionType = iota + MissingFiles +) + +// ErrCorrupted is the type that wraps errors that indicate corruption in +// the database. +type ErrCorrupted struct { + Type CorruptionType + Err error +} + +func (e ErrCorrupted) Error() string { + return e.Err.Error() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go index d7dff04b..cedee793 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go @@ -21,7 +21,7 @@ var _ = testutil.Defer(func() { BlockRestartInterval: 5, BlockSize: 50, Compression: opt.NoCompression, - MaxOpenFiles: 0, + CachedOpenFiles: -1, Strict: opt.StrictAll, WriteBuffer: 1000, } @@ -36,22 +36,21 @@ var _ = testutil.Defer(func() { testutil.DoDBTesting(&t) db.TestClose() done <- true - }, 9.0) + }, 20.0) }) Describe("read test", func() { - testutil.AllKeyValueTesting(nil, func(kv testutil.KeyValue) testutil.DB { + testutil.AllKeyValueTesting(nil, nil, func(kv testutil.KeyValue) testutil.DB { // Building the DB. db := newTestingDB(o, nil, nil) kv.IterateShuffled(nil, func(i int, key, value []byte) { err := db.TestPut(key, value) Expect(err).NotTo(HaveOccurred()) }) - testutil.Defer("teardown", func() { - db.TestClose() - }) return db + }, func(db testutil.DB) { + db.(*testingDB).TestClose() }) }) }) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/go13_bench_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/go13_bench_test.go new file mode 100644 index 00000000..e76657e5 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/go13_bench_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2012, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// +build go1.3 + +package leveldb + +import ( + "sync/atomic" + "testing" +) + +func BenchmarkDBReadConcurrent(b *testing.B) { + p := openDBBench(b, false) + p.populate(b.N) + p.fill() + p.gc() + defer p.close() + + b.ResetTimer() + b.SetBytes(116) + + b.RunParallel(func(pb *testing.PB) { + iter := p.newIter() + defer iter.Release() + for pb.Next() && iter.Next() { + } + }) +} + +func BenchmarkDBReadConcurrent2(b *testing.B) { + p := openDBBench(b, false) + p.populate(b.N) + p.fill() + p.gc() + defer p.close() + + b.ResetTimer() + b.SetBytes(116) + + var dir uint32 + b.RunParallel(func(pb *testing.PB) { + iter := p.newIter() + defer iter.Release() + if atomic.AddUint32(&dir, 1)%2 == 0 { + for pb.Next() && iter.Next() { + } + } else { + if pb.Next() && iter.Last() { + for pb.Next() && iter.Prev() { + } + } + } + }) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter.go index 9b4b7274..a23ab05f 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter.go @@ -40,13 +40,19 @@ type basicArrayIterator struct { util.BasicReleaser array BasicArray pos int + err error } func (i *basicArrayIterator) Valid() bool { - return i.pos >= 0 && i.pos < i.array.Len() + return i.pos >= 0 && i.pos < i.array.Len() && !i.Released() } func (i *basicArrayIterator) First() bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + if i.array.Len() == 0 { i.pos = -1 return false @@ -56,6 +62,11 @@ func (i *basicArrayIterator) First() bool { } func (i *basicArrayIterator) Last() bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + n := i.array.Len() if n == 0 { i.pos = 0 @@ -66,6 +77,11 @@ func (i *basicArrayIterator) Last() bool { } func (i *basicArrayIterator) Seek(key []byte) bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + n := i.array.Len() if n == 0 { i.pos = 0 @@ -79,6 +95,11 @@ func (i *basicArrayIterator) Seek(key []byte) bool { } func (i *basicArrayIterator) Next() bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + i.pos++ if n := i.array.Len(); i.pos >= n { i.pos = n @@ -88,6 +109,11 @@ func (i *basicArrayIterator) Next() bool { } func (i *basicArrayIterator) Prev() bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + i.pos-- if i.pos < 0 { i.pos = -1 @@ -96,7 +122,7 @@ func (i *basicArrayIterator) Prev() bool { return true } -func (i *basicArrayIterator) Error() error { return nil } +func (i *basicArrayIterator) Error() error { return i.err } type arrayIterator struct { basicArrayIterator diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go index 1e99a2bf..8353b357 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go @@ -26,9 +26,10 @@ type indexedIterator struct { strict bool strictGet bool - data Iterator - err error - errf func(err error) + data Iterator + err error + errf func(err error) + closed bool } func (i *indexedIterator) setData() { @@ -50,6 +51,15 @@ func (i *indexedIterator) clearData() { i.data = nil } +func (i *indexedIterator) indexErr() { + if err := i.index.Error(); err != nil { + if i.errf != nil { + i.errf(err) + } + i.err = err + } +} + func (i *indexedIterator) dataErr() bool { if i.errf != nil { if err := i.data.Error(); err != nil { @@ -72,9 +82,13 @@ func (i *indexedIterator) Valid() bool { func (i *indexedIterator) First() bool { if i.err != nil { return false + } else if i.Released() { + i.err = ErrIterReleased + return false } if !i.index.First() { + i.indexErr() i.clearData() return false } @@ -85,9 +99,13 @@ func (i *indexedIterator) First() bool { func (i *indexedIterator) Last() bool { if i.err != nil { return false + } else if i.Released() { + i.err = ErrIterReleased + return false } if !i.index.Last() { + i.indexErr() i.clearData() return false } @@ -105,9 +123,13 @@ func (i *indexedIterator) Last() bool { func (i *indexedIterator) Seek(key []byte) bool { if i.err != nil { return false + } else if i.Released() { + i.err = ErrIterReleased + return false } if !i.index.Seek(key) { + i.indexErr() i.clearData() return false } @@ -125,6 +147,9 @@ func (i *indexedIterator) Seek(key []byte) bool { func (i *indexedIterator) Next() bool { if i.err != nil { return false + } else if i.Released() { + i.err = ErrIterReleased + return false } switch { @@ -136,6 +161,7 @@ func (i *indexedIterator) Next() bool { fallthrough case i.data == nil: if !i.index.Next() { + i.indexErr() return false } i.setData() @@ -147,6 +173,9 @@ func (i *indexedIterator) Next() bool { func (i *indexedIterator) Prev() bool { if i.err != nil { return false + } else if i.Released() { + i.err = ErrIterReleased + return false } switch { @@ -158,6 +187,7 @@ func (i *indexedIterator) Prev() bool { fallthrough case i.data == nil: if !i.index.Prev() { + i.indexErr() return false } i.setData() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter.go index 1b80184e..c2522860 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter.go @@ -14,6 +14,10 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) +var ( + ErrIterReleased = errors.New("leveldb/iterator: iterator released") +) + // IteratorSeeker is the interface that wraps the 'seeks method'. type IteratorSeeker interface { // First moves the iterator to the first key/value pair. If the iterator @@ -100,28 +104,13 @@ type ErrorCallbackSetter interface { } type emptyIterator struct { - releaser util.Releaser - released bool - err error + util.BasicReleaser + err error } func (i *emptyIterator) rErr() { - if i.err == nil && i.released { - i.err = errors.New("leveldb/iterator: iterator released") - } -} - -func (i *emptyIterator) Release() { - if i.releaser != nil { - i.releaser.Release() - i.releaser = nil - } - i.released = true -} - -func (i *emptyIterator) SetReleaser(releaser util.Releaser) { - if !i.released { - i.releaser = releaser + if i.err == nil && i.Released() { + i.err = ErrIterReleased } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go index c8314c4e..8370e25e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go @@ -7,16 +7,10 @@ package iterator import ( - "errors" - "github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/util" ) -var ( - ErrIterReleased = errors.New("leveldb/iterator: iterator released") -) - type dir int const ( @@ -274,9 +268,13 @@ func (i *mergedIterator) Release() { } func (i *mergedIterator) SetReleaser(releaser util.Releaser) { - if i.dir != dirReleased { - i.releaser = releaser + if i.dir == dirReleased { + panic(util.ErrReleased) } + if i.releaser != nil && releaser != nil { + panic(util.ErrHasReleaser) + } + i.releaser = releaser } func (i *mergedIterator) Error() error { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go index b522c76e..e9a19ebc 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go @@ -103,18 +103,18 @@ type flusher interface { Flush() error } -// DroppedError is the error type that passed to Dropper.Drop method. -type DroppedError struct { +// ErrCorrupted is the error type that generated by corrupted block or chunk. +type ErrCorrupted struct { Size int Reason string } -func (e DroppedError) Error() string { - return fmt.Sprintf("leveldb/journal: dropped %d bytes: %s", e.Size, e.Reason) +func (e ErrCorrupted) Error() string { + return fmt.Sprintf("leveldb/journal: block/chunk corrupted: %s (%d bytes)", e.Reason, e.Size) } // Dropper is the interface that wrap simple Drop method. The Drop -// method will be called when the journal reader dropping a chunk. +// method will be called when the journal reader dropping a block or chunk. type Dropper interface { Drop(err error) } @@ -158,76 +158,78 @@ func NewReader(r io.Reader, dropper Dropper, strict, checksum bool) *Reader { } } +var errSkip = errors.New("leveldb/journal: skipped") + +func (r *Reader) corrupt(n int, reason string, skip bool) error { + if r.dropper != nil { + r.dropper.Drop(ErrCorrupted{n, reason}) + } + if r.strict && !skip { + r.err = ErrCorrupted{n, reason} + return r.err + } + return errSkip +} + // nextChunk sets r.buf[r.i:r.j] to hold the next chunk's payload, reading the // next block into the buffer if necessary. -func (r *Reader) nextChunk(wantFirst, skip bool) error { +func (r *Reader) nextChunk(first bool) error { for { if r.j+headerSize <= r.n { checksum := binary.LittleEndian.Uint32(r.buf[r.j+0 : r.j+4]) length := binary.LittleEndian.Uint16(r.buf[r.j+4 : r.j+6]) chunkType := r.buf[r.j+6] - var err error if checksum == 0 && length == 0 && chunkType == 0 { // Drop entire block. - err = DroppedError{r.n - r.j, "zero header"} + m := r.n - r.j r.i = r.n r.j = r.n + return r.corrupt(m, "zero header", false) } else { m := r.n - r.j r.i = r.j + headerSize r.j = r.j + headerSize + int(length) if r.j > r.n { // Drop entire block. - err = DroppedError{m, "chunk length overflows block"} r.i = r.n r.j = r.n + return r.corrupt(m, "chunk length overflows block", false) } else if r.checksum && checksum != util.NewCRC(r.buf[r.i-1:r.j]).Value() { // Drop entire block. - err = DroppedError{m, "checksum mismatch"} r.i = r.n r.j = r.n + return r.corrupt(m, "checksum mismatch", false) } } - if wantFirst && err == nil && chunkType != fullChunkType && chunkType != firstChunkType { - if skip { - // The chunk are intentionally skipped. - if chunkType == lastChunkType { - skip = false - } - continue - } else { - // Drop the chunk. - err = DroppedError{r.j - r.i + headerSize, "orphan chunk"} - } + if first && chunkType != fullChunkType && chunkType != firstChunkType { + m := r.j - r.i + r.i = r.j + // Report the error, but skip it. + return r.corrupt(m+headerSize, "orphan chunk", true) } - if err == nil { - r.last = chunkType == fullChunkType || chunkType == lastChunkType - } else { - if r.dropper != nil { - r.dropper.Drop(err) - } - if r.strict { - r.err = err - } + r.last = chunkType == fullChunkType || chunkType == lastChunkType + return nil + } + + // The last block. + if r.n < blockSize && r.n > 0 { + if !first { + return r.corrupt(0, "missing chunk part", false) } + r.err = io.EOF + return r.err + } + + // Read block. + n, err := io.ReadFull(r.r, r.buf[:]) + if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { return err } - if r.n < blockSize && r.n > 0 { - // This is the last block. - if r.j != r.n { - r.err = io.ErrUnexpectedEOF - } else { - r.err = io.EOF - } - return r.err - } - n, err := io.ReadFull(r.r, r.buf[:]) - if err != nil && err != io.ErrUnexpectedEOF { - r.err = err - return r.err - } if n == 0 { + if !first { + return r.corrupt(0, "missing chunk part", false) + } r.err = io.EOF return r.err } @@ -237,29 +239,26 @@ func (r *Reader) nextChunk(wantFirst, skip bool) error { // Next returns a reader for the next journal. It returns io.EOF if there are no // more journals. The reader returned becomes stale after the next Next call, -// and should no longer be used. +// and should no longer be used. If strict is false, the reader will returns +// io.ErrUnexpectedEOF error when found corrupted journal. func (r *Reader) Next() (io.Reader, error) { r.seq++ if r.err != nil { return nil, r.err } - skip := !r.last + r.i = r.j for { - r.i = r.j - if r.nextChunk(true, skip) != nil { - // So that 'orphan chunk' drop will be reported. - skip = false - } else { + if err := r.nextChunk(true); err == nil { break - } - if r.err != nil { - return nil, r.err + } else if err != errSkip { + return nil, err } } return &singleReader{r, r.seq, nil}, nil } -// Reset resets the journal reader, allows reuse of the journal reader. +// Reset resets the journal reader, allows reuse of the journal reader. Reset returns +// last accumulated error. func (r *Reader) Reset(reader io.Reader, dropper Dropper, strict, checksum bool) error { r.seq++ err := r.err @@ -296,7 +295,11 @@ func (x *singleReader) Read(p []byte) (int, error) { if r.last { return 0, io.EOF } - if x.err = r.nextChunk(false, false); x.err != nil { + x.err = r.nextChunk(false) + if x.err != nil { + if x.err == errSkip { + x.err = io.ErrUnexpectedEOF + } return 0, x.err } } @@ -320,7 +323,11 @@ func (x *singleReader) ReadByte() (byte, error) { if r.last { return 0, io.EOF } - if x.err = r.nextChunk(false, false); x.err != nil { + x.err = r.nextChunk(false) + if x.err != nil { + if x.err == errSkip { + x.err = io.ErrUnexpectedEOF + } return 0, x.err } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go index 5e1193ae..0fcf2259 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go @@ -12,6 +12,7 @@ package journal import ( "bytes" + "encoding/binary" "fmt" "io" "io/ioutil" @@ -326,3 +327,492 @@ func TestStaleWriter(t *testing.T) { t.Fatalf("stale write #1: unexpected error: %v", err) } } + +func TestCorrupt_MissingLastBlock(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-1024)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + // Cut the last block. + b := buf.Bytes()[:blockSize] + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read. + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if n != blockSize-1024 { + t.Fatalf("read #0: got %d bytes want %d", n, blockSize-1024) + } + + // Second read. + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != io.ErrUnexpectedEOF { + t.Fatalf("read #1: unexpected error: %v", err) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} + +func TestCorrupt_CorruptedFirstBlock(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + // Third record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil { + t.Fatalf("write #2: unexpected error: %v", err) + } + + // Fourth record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil { + t.Fatalf("write #3: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + b := buf.Bytes() + // Corrupting block #0. + for i := 0; i < 1024; i++ { + b[i] = '1' + } + + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read (third record). + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if want := int64(blockSize-headerSize) + 1; n != want { + t.Fatalf("read #0: got %d bytes want %d", n, want) + } + + // Second read (fourth record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #1: %v", err) + } + if want := int64(blockSize-headerSize) + 2; n != want { + t.Fatalf("read #1: got %d bytes want %d", n, want) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} + +func TestCorrupt_CorruptedMiddleBlock(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + // Third record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil { + t.Fatalf("write #2: unexpected error: %v", err) + } + + // Fourth record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil { + t.Fatalf("write #3: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + b := buf.Bytes() + // Corrupting block #1. + for i := 0; i < 1024; i++ { + b[blockSize+i] = '1' + } + + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read (first record). + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if want := int64(blockSize / 2); n != want { + t.Fatalf("read #0: got %d bytes want %d", n, want) + } + + // Second read (second record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != io.ErrUnexpectedEOF { + t.Fatalf("read #1: unexpected error: %v", err) + } + + // Third read (fourth record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #2: %v", err) + } + if want := int64(blockSize-headerSize) + 2; n != want { + t.Fatalf("read #2: got %d bytes want %d", n, want) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} + +func TestCorrupt_CorruptedLastBlock(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + // Third record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil { + t.Fatalf("write #2: unexpected error: %v", err) + } + + // Fourth record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil { + t.Fatalf("write #3: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + b := buf.Bytes() + // Corrupting block #3. + for i := len(b) - 1; i > len(b)-1024; i-- { + b[i] = '1' + } + + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read (first record). + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if want := int64(blockSize / 2); n != want { + t.Fatalf("read #0: got %d bytes want %d", n, want) + } + + // Second read (second record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #1: %v", err) + } + if want := int64(blockSize - headerSize); n != want { + t.Fatalf("read #1: got %d bytes want %d", n, want) + } + + // Third read (third record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #2: %v", err) + } + if want := int64(blockSize-headerSize) + 1; n != want { + t.Fatalf("read #2: got %d bytes want %d", n, want) + } + + // Fourth read (fourth record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != io.ErrUnexpectedEOF { + t.Fatalf("read #3: unexpected error: %v", err) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} + +func TestCorrupt_FirstChuckLengthOverflow(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + // Third record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil { + t.Fatalf("write #2: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + b := buf.Bytes() + // Corrupting record #1. + x := blockSize + binary.LittleEndian.PutUint16(b[x+4:], 0xffff) + + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read (first record). + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if want := int64(blockSize / 2); n != want { + t.Fatalf("read #0: got %d bytes want %d", n, want) + } + + // Second read (second record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != io.ErrUnexpectedEOF { + t.Fatalf("read #1: unexpected error: %v", err) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} + +func TestCorrupt_MiddleChuckLengthOverflow(t *testing.T) { + buf := new(bytes.Buffer) + + w := NewWriter(buf) + + // First record. + ww, err := w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil { + t.Fatalf("write #0: unexpected error: %v", err) + } + + // Second record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil { + t.Fatalf("write #1: unexpected error: %v", err) + } + + // Third record. + ww, err = w.Next() + if err != nil { + t.Fatal(err) + } + if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil { + t.Fatalf("write #2: unexpected error: %v", err) + } + + if err := w.Close(); err != nil { + t.Fatal(err) + } + + b := buf.Bytes() + // Corrupting record #1. + x := blockSize/2 + headerSize + binary.LittleEndian.PutUint16(b[x+4:], 0xffff) + + r := NewReader(bytes.NewReader(b), dropper{t}, false, true) + + // First read (first record). + rr, err := r.Next() + if err != nil { + t.Fatal(err) + } + n, err := io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #0: %v", err) + } + if want := int64(blockSize / 2); n != want { + t.Fatalf("read #0: got %d bytes want %d", n, want) + } + + // Second read (third record). + rr, err = r.Next() + if err != nil { + t.Fatal(err) + } + n, err = io.Copy(ioutil.Discard, rr) + if err != nil { + t.Fatalf("read #1: %v", err) + } + if want := int64(blockSize-headerSize) + 1; n != want { + t.Fatalf("read #1: got %d bytes want %d", n, want) + } + + if _, err := r.Next(); err != io.EOF { + t.Fatalf("last next: unexpected error: %v", err) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go index b65664ae..e307cfc1 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go @@ -13,7 +13,7 @@ import ( "github.com/syndtr/goleveldb/leveldb/comparer" ) -var icmp = &iComparer{comparer.DefaultComparer} +var defaultIComparer = &iComparer{comparer.DefaultComparer} func ikey(key string, seq uint64, t vType) iKey { return newIKey([]byte(key), uint64(seq), t) @@ -21,7 +21,7 @@ func ikey(key string, seq uint64, t vType) iKey { func shortSep(a, b []byte) []byte { dst := make([]byte, len(a)) - dst = icmp.Separator(dst[:0], a, b) + dst = defaultIComparer.Separator(dst[:0], a, b) if dst == nil { return a } @@ -30,7 +30,7 @@ func shortSep(a, b []byte) []byte { func shortSuccessor(b []byte) []byte { dst := make([]byte, len(b)) - dst = icmp.Successor(dst[:0], b) + dst = defaultIComparer.Successor(dst[:0], b) if dst == nil { return b } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go index 7bcae992..83ff7bc6 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go @@ -8,6 +8,7 @@ package memdb import ( + "errors" "math/rand" "sync" @@ -17,7 +18,8 @@ import ( ) var ( - ErrNotFound = util.ErrNotFound + ErrNotFound = util.ErrNotFound + ErrIterReleased = errors.New("leveldb/memdb: iterator released") ) const tMaxHeight = 12 @@ -29,6 +31,7 @@ type dbIter struct { node int forward bool key, value []byte + err error } func (i *dbIter) fill(checkStart, checkLimit bool) bool { @@ -59,6 +62,11 @@ func (i *dbIter) Valid() bool { } func (i *dbIter) First() bool { + if i.Released() { + i.err = ErrIterReleased + return false + } + i.forward = true i.p.mu.RLock() defer i.p.mu.RUnlock() @@ -71,9 +79,11 @@ func (i *dbIter) First() bool { } func (i *dbIter) Last() bool { - if i.p == nil { + if i.Released() { + i.err = ErrIterReleased return false } + i.forward = false i.p.mu.RLock() defer i.p.mu.RUnlock() @@ -86,9 +96,11 @@ func (i *dbIter) Last() bool { } func (i *dbIter) Seek(key []byte) bool { - if i.p == nil { + if i.Released() { + i.err = ErrIterReleased return false } + i.forward = true i.p.mu.RLock() defer i.p.mu.RUnlock() @@ -100,9 +112,11 @@ func (i *dbIter) Seek(key []byte) bool { } func (i *dbIter) Next() bool { - if i.p == nil { + if i.Released() { + i.err = ErrIterReleased return false } + if i.node == 0 { if !i.forward { return i.First() @@ -117,9 +131,11 @@ func (i *dbIter) Next() bool { } func (i *dbIter) Prev() bool { - if i.p == nil { + if i.Released() { + i.err = ErrIterReleased return false } + if i.node == 0 { if i.forward { return i.Last() @@ -141,10 +157,10 @@ func (i *dbIter) Value() []byte { return i.value } -func (i *dbIter) Error() error { return nil } +func (i *dbIter) Error() error { return i.err } func (i *dbIter) Release() { - if i.p != nil { + if !i.Released() { i.p = nil i.node = 0 i.key = nil @@ -437,6 +453,8 @@ func (p *DB) Reset() { // New creates a new initalized in-memory key/value DB. The capacity // is the initial key/value buffer capacity. The capacity is advisory, // not enforced. +// +// The returned DB instance is goroutine-safe. func New(cmp comparer.BasicComparer, capacity int) *DB { p := &DB{ cmp: cmp, diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go index f96a9d1e..5dd6dbc7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go @@ -129,7 +129,7 @@ var _ = testutil.Defer(func() { } return db - }) + }, nil, nil) }) }) }) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go index b940ce42..2a375ba6 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go @@ -24,16 +24,22 @@ const ( DefaultBlockRestartInterval = 16 DefaultBlockSize = 4 * KiB DefaultCompressionType = SnappyCompression - DefaultMaxOpenFiles = 1000 + DefaultCachedOpenFiles = 500 DefaultWriteBuffer = 4 * MiB ) type noCache struct{} -func (noCache) SetCapacity(capacity int) {} -func (noCache) GetNamespace(id uint64) cache.Namespace { return nil } -func (noCache) Purge(fin cache.PurgeFin) {} -func (noCache) Zap(closed bool) {} +func (noCache) SetCapacity(capacity int) {} +func (noCache) Capacity() int { return 0 } +func (noCache) Used() int { return 0 } +func (noCache) Size() int { return 0 } +func (noCache) NumObjects() int { return 0 } +func (noCache) GetNamespace(id uint64) cache.Namespace { return nil } +func (noCache) PurgeNamespace(id uint64, fin cache.PurgeFin) {} +func (noCache) ZapNamespace(id uint64) {} +func (noCache) Purge(fin cache.PurgeFin) {} +func (noCache) Zap() {} var NoCache cache.Cache = noCache{} @@ -119,6 +125,13 @@ type Options struct { // The default value is 4KiB. BlockSize int + // CachedOpenFiles defines number of open files to kept around when not + // in-use, the counting includes still in-use files. + // Set this to negative value to disable caching. + // + // The default value is 500. + CachedOpenFiles int + // Comparer defines a total ordering over the space of []byte keys: a 'less // than' relationship. The same comparison algorithm must be used for reads // and writes over the lifetime of the DB. @@ -159,13 +172,6 @@ type Options struct { // The default value is nil. Filter filter.Filter - // MaxOpenFiles defines maximum number of open files to kept around - // (cached). This is not an hard limit, actual open files may exceed - // the defined value. - // - // The default value is 1000. - MaxOpenFiles int - // Strict defines the DB strict level. Strict Strict @@ -207,6 +213,15 @@ func (o *Options) GetBlockSize() int { return o.BlockSize } +func (o *Options) GetCachedOpenFiles() int { + if o == nil || o.CachedOpenFiles == 0 { + return DefaultCachedOpenFiles + } else if o.CachedOpenFiles < 0 { + return 0 + } + return o.CachedOpenFiles +} + func (o *Options) GetComparer() comparer.Comparer { if o == nil || o.Comparer == nil { return comparer.DefaultComparer @@ -242,13 +257,6 @@ func (o *Options) GetFilter() filter.Filter { return o.Filter } -func (o *Options) GetMaxOpenFiles() int { - if o == nil || o.MaxOpenFiles <= 0 { - return DefaultMaxOpenFiles - } - return o.MaxOpenFiles -} - func (o *Options) GetStrict(strict Strict) bool { if o == nil || o.Strict == 0 { return DefaultStrict&strict != 0 diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go index 480c2d0a..fc6a9696 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go @@ -32,8 +32,8 @@ func (s *session) setOptions(o *opt.Options) { s.o.BlockCache = nil } // Comparer. - s.cmp = &iComparer{o.GetComparer()} - s.o.Comparer = s.cmp + s.icmp = &iComparer{o.GetComparer()} + s.o.Comparer = s.icmp // Filter. if filter := o.GetFilter(); filter != nil { s.o.Filter = &iFilter{filter} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go index e940915a..7fc08e7d 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go @@ -20,16 +20,6 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -// ErrManifest is the type that wraps errors produced by missing -// or corrupted manifest file. -type ErrManifest struct { - Err error -} - -func (e ErrManifest) Error() string { - return e.Err.Error() -} - // session represent a persistent database session. type session struct { // Need 64-bit alignment. @@ -42,18 +32,19 @@ type session struct { stor storage.Storage storLock util.Releaser o *opt.Options - cmp *iComparer + icmp *iComparer tops *tOps manifest *journal.Writer manifestWriter storage.Writer manifestFile storage.File - stCPtrs [kNumLevels]iKey // compact pointers; need external synchronization + stCptrs [kNumLevels]iKey // compact pointers; need external synchronization stVersion *version // current version vmu sync.Mutex } +// Creates new initialized session instance. func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) { if stor == nil { return nil, os.ErrInvalid @@ -67,7 +58,7 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) { storLock: storLock, } s.setOptions(o) - s.tops = newTableOps(s, s.o.GetMaxOpenFiles()) + s.tops = newTableOps(s, s.o.GetCachedOpenFiles()) s.setVersion(&version{s: s}) s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock D·DeletedEntry L·Level Q·SeqNum T·TimeElapsed") return @@ -91,6 +82,7 @@ func (s *session) close() { s.stVersion = nil } +// Release session lock. func (s *session) release() { s.storLock.Release() } @@ -108,7 +100,7 @@ func (s *session) recover() (err error) { // Don't return os.ErrNotExist if the underlying storage contains // other files that belong to LevelDB. So the DB won't get trashed. if files, _ := s.stor.GetFiles(storage.TypeAll); len(files) > 0 { - err = ErrManifest{Err: errors.New("leveldb: manifest file missing")} + err = ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest file missing")} } } }() @@ -142,13 +134,13 @@ func (s *session) recover() (err error) { err = rec.decode(r) if err == nil { // save compact pointers - for _, rp := range rec.compactionPointers { - s.stCPtrs[rp.level] = iKey(rp.key) + for _, r := range rec.compactionPointers { + s.stCptrs[r.level] = iKey(r.ikey) } // commit record to version staging staging.commit(rec) } else if strict { - return ErrManifest{Err: err} + return ErrCorrupted{Type: CorruptedManifest, Err: err} } else { s.logf("manifest error: %v (skipped)", err) } @@ -159,15 +151,15 @@ func (s *session) recover() (err error) { switch { case !rec.has(recComparer): - return ErrManifest{Err: errors.New("leveldb: manifest missing comparer name")} - case rec.comparer != s.cmp.cmp.Name(): - return ErrManifest{Err: errors.New("leveldb: comparer mismatch, " + "want '" + s.cmp.cmp.Name() + "', " + "got '" + rec.comparer + "'")} + return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing comparer name")} + case rec.comparer != s.icmp.uName(): + return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: comparer mismatch, " + "want '" + s.icmp.uName() + "', " + "got '" + rec.comparer + "'")} case !rec.has(recNextNum): - return ErrManifest{Err: errors.New("leveldb: manifest missing next file number")} + return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing next file number")} case !rec.has(recJournalNum): - return ErrManifest{Err: errors.New("leveldb: manifest missing journal file number")} + return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing journal file number")} case !rec.has(recSeq): - return ErrManifest{Err: errors.New("leveldb: manifest missing seq number")} + return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing seq number")} } s.manifestFile = file @@ -199,25 +191,22 @@ func (s *session) commit(r *sessionRecord) (err error) { // Pick a compaction based on current state; need external synchronization. func (s *session) pickCompaction() *compaction { - icmp := s.cmp - ucmp := icmp.cmp - v := s.version_NB() var level int var t0 tFiles if v.cScore >= 1 { level = v.cLevel - cp := s.stCPtrs[level] - tt := v.tables[level] - for _, t := range tt { - if cp == nil || icmp.Compare(t.max, cp) > 0 { + cptr := s.stCptrs[level] + tables := v.tables[level] + for _, t := range tables { + if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 { t0 = append(t0, t) break } } if len(t0) == 0 { - t0 = append(t0, tt[0]) + t0 = append(t0, tables[0]) } } else { if p := atomic.LoadPointer(&v.cSeek); p != nil { @@ -229,11 +218,10 @@ func (s *session) pickCompaction() *compaction { } } - c := &compaction{s: s, version: v, level: level} + c := &compaction{s: s, v: v, level: level} if level == 0 { - min, max := t0.getRange(icmp) - t0 = nil - v.tables[0].getOverlaps(min.ukey(), max.ukey(), &t0, false, ucmp) + imin, imax := t0.getRange(s.icmp) + t0 = v.tables[0].getOverlaps(t0[:0], s.icmp, imin.ukey(), imax.ukey(), true) } c.tables[0] = t0 @@ -242,25 +230,41 @@ func (s *session) pickCompaction() *compaction { } // Create compaction from given level and range; need external synchronization. -func (s *session) getCompactionRange(level int, min, max []byte) *compaction { +func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction { v := s.version_NB() - var t0 tFiles - v.tables[level].getOverlaps(min, max, &t0, level != 0, s.cmp.cmp) + t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0) if len(t0) == 0 { return nil } - c := &compaction{s: s, version: v, level: level} + // Avoid compacting too much in one shot in case the range is large. + // But we cannot do this for level-0 since level-0 files can overlap + // and we must not pick one file and drop another older file if the + // two files overlap. + if level > 0 { + limit := uint64(kMaxTableSize) + total := uint64(0) + for i, t := range t0 { + total += t.size + if total >= limit { + s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1) + t0 = t0[:i+1] + break + } + } + } + + c := &compaction{s: s, v: v, level: level} c.tables[0] = t0 c.expand() return c } -// compaction represent a compaction state +// compaction represent a compaction state. type compaction struct { - s *session - version *version + s *session + v *version level int tables [2]tFiles @@ -269,44 +273,36 @@ type compaction struct { gpidx int seenKey bool overlappedBytes uint64 - min, max iKey + imin, imax iKey tPtrs [kNumLevels]int } // Expand compacted tables; need external synchronization. func (c *compaction) expand() { - s := c.s - v := c.version - icmp := s.cmp - ucmp := icmp.cmp - level := c.level - vt0, vt1 := v.tables[level], v.tables[level+1] + vt0, vt1 := c.v.tables[level], c.v.tables[level+1] t0, t1 := c.tables[0], c.tables[1] - min, max := t0.getRange(icmp) - vt1.getOverlaps(min.ukey(), max.ukey(), &t1, true, ucmp) - - // Get entire range covered by compaction - amin, amax := append(t0, t1...).getRange(icmp) + imin, imax := t0.getRange(c.s.icmp) + t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false) + // Get entire range covered by compaction. + amin, amax := append(t0, t1...).getRange(c.s.icmp) // See if we can grow the number of inputs in "level" without // changing the number of "level+1" files we pick up. if len(t1) > 0 { - var exp0 tFiles - vt0.getOverlaps(amin.ukey(), amax.ukey(), &exp0, level != 0, ucmp) + exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), level == 0) if len(exp0) > len(t0) && t1.size()+exp0.size() < kExpCompactionMaxBytes { - var exp1 tFiles - xmin, xmax := exp0.getRange(icmp) - vt1.getOverlaps(xmin.ukey(), xmax.ukey(), &exp1, true, ucmp) + xmin, xmax := exp0.getRange(c.s.icmp) + exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false) if len(exp1) == len(t1) { - s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)", + c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)", level, level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())), len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size()))) - min, max = xmin, xmax + imin, imax = xmin, xmax t0, t1 = exp0, exp1 - amin, amax = append(t0, t1...).getRange(icmp) + amin, amax = append(t0, t1...).getRange(c.s.icmp) } } } @@ -314,11 +310,11 @@ func (c *compaction) expand() { // Compute the set of grandparent files that overlap this compaction // (parent == level+1; grandparent == level+2) if level+2 < kNumLevels { - v.tables[level+2].getOverlaps(amin.ukey(), amax.ukey(), &c.gp, true, ucmp) + c.gp = c.v.tables[level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false) } c.tables[0], c.tables[1] = t0, t1 - c.min, c.max = min, max + c.imin, c.imax = imin, imax } // Check whether compaction is trivial. @@ -326,17 +322,14 @@ func (c *compaction) trivial() bool { return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= kMaxGrandParentOverlapBytes } -func (c *compaction) isBaseLevelForKey(key []byte) bool { - s := c.s - v := c.version - ucmp := s.cmp.cmp - for level, tt := range v.tables[c.level+2:] { - for c.tPtrs[level] < len(tt) { - t := tt[c.tPtrs[level]] - if ucmp.Compare(key, t.max.ukey()) <= 0 { - // We've advanced far enough - if ucmp.Compare(key, t.min.ukey()) >= 0 { - // Key falls in this file's range, so definitely not base level +func (c *compaction) baseLevelForKey(ukey []byte) bool { + for level, tables := range c.v.tables[c.level+2:] { + for c.tPtrs[level] < len(tables) { + t := tables[c.tPtrs[level]] + if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 { + // We've advanced far enough. + if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 { + // Key falls in this file's range, so definitely not base level. return false } break @@ -347,11 +340,10 @@ func (c *compaction) isBaseLevelForKey(key []byte) bool { return true } -func (c *compaction) shouldStopBefore(key iKey) bool { - icmp := c.s.cmp +func (c *compaction) shouldStopBefore(ikey iKey) bool { for ; c.gpidx < len(c.gp); c.gpidx++ { gp := c.gp[c.gpidx] - if icmp.Compare(key, gp.max) <= 0 { + if c.s.icmp.Compare(ikey, gp.imax) <= 0 { break } if c.seenKey { @@ -361,43 +353,44 @@ func (c *compaction) shouldStopBefore(key iKey) bool { c.seenKey = true if c.overlappedBytes > kMaxGrandParentOverlapBytes { - // Too much overlap for current output; start new output + // Too much overlap for current output; start new output. c.overlappedBytes = 0 return true } return false } +// Creates an iterator. func (c *compaction) newIterator() iterator.Iterator { - s := c.s - icmp := s.cmp - - level := c.level - icap := 2 + // Creates iterator slice. + icap := len(c.tables) if c.level == 0 { + // Special case for level-0 icap = len(c.tables[0]) + 1 } its := make([]iterator.Iterator, 0, icap) + // Options. ro := &opt.ReadOptions{ DontFillCache: true, } - strict := s.o.GetStrict(opt.StrictIterator) + strict := c.s.o.GetStrict(opt.StrictIterator) - for i, tt := range c.tables { - if len(tt) == 0 { + for i, tables := range c.tables { + if len(tables) == 0 { continue } - if level+i == 0 { - for _, t := range tt { - its = append(its, s.tops.newIterator(t, nil, ro)) + // Level-0 is not sorted and may overlaps each other. + if c.level+i == 0 { + for _, t := range tables { + its = append(its, c.s.tops.newIterator(t, nil, ro)) } } else { - it := iterator.NewIndexedIterator(tt.newIndexIterator(s.tops, icmp, nil, ro), strict, true) + it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict, true) its = append(its, it) } } - return iterator.NewMergedIterator(its, icmp, true) + return iterator.NewMergedIterator(its, c.s.icmp, true) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go index c50fda73..27212958 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go @@ -35,19 +35,19 @@ const ( type cpRecord struct { level int - key iKey + ikey iKey } type ntRecord struct { level int num uint64 size uint64 - min iKey - max iKey + imin iKey + imax iKey } func (r ntRecord) makeFile(s *session) *tFile { - return newTFile(s.getTableFile(r.num), r.size, r.min, r.max) + return newTableFile(s.getTableFile(r.num), r.size, r.imin, r.imax) } type dtRecord struct { @@ -98,9 +98,9 @@ func (p *sessionRecord) setSeq(seq uint64) { p.seq = seq } -func (p *sessionRecord) addCompactionPointer(level int, key iKey) { +func (p *sessionRecord) addCompactionPointer(level int, ikey iKey) { p.hasRec |= 1 << recCompactionPointer - p.compactionPointers = append(p.compactionPointers, cpRecord{level, key}) + p.compactionPointers = append(p.compactionPointers, cpRecord{level, ikey}) } func (p *sessionRecord) resetCompactionPointers() { @@ -108,13 +108,13 @@ func (p *sessionRecord) resetCompactionPointers() { p.compactionPointers = p.compactionPointers[:0] } -func (p *sessionRecord) addTable(level int, num, size uint64, min, max iKey) { +func (p *sessionRecord) addTable(level int, num, size uint64, imin, imax iKey) { p.hasRec |= 1 << recNewTable - p.addedTables = append(p.addedTables, ntRecord{level, num, size, min, max}) + p.addedTables = append(p.addedTables, ntRecord{level, num, size, imin, imax}) } func (p *sessionRecord) addTableFile(level int, t *tFile) { - p.addTable(level, t.file.Num(), t.size, t.min, t.max) + p.addTable(level, t.file.Num(), t.size, t.imin, t.imax) } func (p *sessionRecord) resetAddedTables() { @@ -169,23 +169,23 @@ func (p *sessionRecord) encode(w io.Writer) error { p.putUvarint(w, recSeq) p.putUvarint(w, p.seq) } - for _, cp := range p.compactionPointers { + for _, r := range p.compactionPointers { p.putUvarint(w, recCompactionPointer) - p.putUvarint(w, uint64(cp.level)) - p.putBytes(w, cp.key) + p.putUvarint(w, uint64(r.level)) + p.putBytes(w, r.ikey) } - for _, t := range p.deletedTables { + for _, r := range p.deletedTables { p.putUvarint(w, recDeletedTable) - p.putUvarint(w, uint64(t.level)) - p.putUvarint(w, t.num) + p.putUvarint(w, uint64(r.level)) + p.putUvarint(w, r.num) } - for _, t := range p.addedTables { + for _, r := range p.addedTables { p.putUvarint(w, recNewTable) - p.putUvarint(w, uint64(t.level)) - p.putUvarint(w, t.num) - p.putUvarint(w, t.size) - p.putBytes(w, t.min) - p.putBytes(w, t.max) + p.putUvarint(w, uint64(r.level)) + p.putUvarint(w, r.num) + p.putUvarint(w, r.size) + p.putBytes(w, r.imin) + p.putBytes(w, r.imax) } return p.err } @@ -282,18 +282,18 @@ func (p *sessionRecord) decode(r io.Reader) error { } case recCompactionPointer: level := p.readLevel(br) - key := p.readBytes(br) + ikey := p.readBytes(br) if p.err == nil { - p.addCompactionPointer(level, iKey(key)) + p.addCompactionPointer(level, iKey(ikey)) } case recNewTable: level := p.readLevel(br) num := p.readUvarint(br) size := p.readUvarint(br) - min := p.readBytes(br) - max := p.readBytes(br) + imin := p.readBytes(br) + imax := p.readBytes(br) if p.err == nil { - p.addTable(level, num, size, min, max) + p.addTable(level, num, size, imin, imax) } case recDeletedTable: level := p.readLevel(br) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go index 863603d0..715c9f5b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go @@ -14,7 +14,7 @@ import ( "github.com/syndtr/goleveldb/leveldb/storage" ) -// logging +// Logging. type dropper struct { s *session @@ -22,22 +22,17 @@ type dropper struct { } func (d dropper) Drop(err error) { - if e, ok := err.(journal.DroppedError); ok { + if e, ok := err.(journal.ErrCorrupted); ok { d.s.logf("journal@drop %s-%d S·%s %q", d.file.Type(), d.file.Num(), shortenb(e.Size), e.Reason) } else { d.s.logf("journal@drop %s-%d %q", d.file.Type(), d.file.Num(), err) } } -func (s *session) log(v ...interface{}) { - s.stor.Log(fmt.Sprint(v...)) -} +func (s *session) log(v ...interface{}) { s.stor.Log(fmt.Sprint(v...)) } +func (s *session) logf(format string, v ...interface{}) { s.stor.Log(fmt.Sprintf(format, v...)) } -func (s *session) logf(format string, v ...interface{}) { - s.stor.Log(fmt.Sprintf(format, v...)) -} - -// file utils +// File utils. func (s *session) getJournalFile(num uint64) storage.File { return s.stor.GetFile(num, storage.TypeJournal) @@ -56,7 +51,7 @@ func (s *session) newTemp() storage.File { return s.stor.GetFile(num, storage.TypeTemp) } -// session state +// Session state. // Get current version. func (s *session) version() *version { @@ -126,7 +121,7 @@ func (s *session) reuseFileNum(num uint64) { } } -// manifest related utils +// Manifest related utils. // Fill given session record obj with current states; need external // synchronization. @@ -142,17 +137,17 @@ func (s *session) fillRecord(r *sessionRecord, snapshot bool) { r.setSeq(s.stSeq) } - for level, ik := range s.stCPtrs { + for level, ik := range s.stCptrs { if ik != nil { r.addCompactionPointer(level, ik) } } - r.setComparer(s.cmp.cmp.Name()) + r.setComparer(s.icmp.uName()) } } -// Mark if record has been commited, this will update session state; +// Mark if record has been committed, this will update session state; // need external synchronization. func (s *session) recordCommited(r *sessionRecord) { if r.has(recJournalNum) { @@ -168,7 +163,7 @@ func (s *session) recordCommited(r *sessionRecord) { } for _, p := range r.compactionPointers { - s.stCPtrs[p.level] = iKey(p.key) + s.stCptrs[p.level] = iKey(p.ikey) } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go index aef620db..75439f6d 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go @@ -343,6 +343,20 @@ type fileWrap struct { f *file } +func (fw fileWrap) Sync() error { + if err := fw.File.Sync(); err != nil { + return err + } + if fw.f.Type() == TypeManifest { + // Also sync parent directory if file type is manifest. + // See: https://code.google.com/p/leveldb/issues/detail?id=190. + if err := syncDir(fw.f.fs.path); err != nil { + return err + } + } + return nil +} + func (fw fileWrap) Close() error { f := fw.f f.fs.mu.Lock() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go index 9e3f19b2..42940d76 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go @@ -38,3 +38,15 @@ func rename(oldpath, newpath string) error { _, fname := filepath.Split(newpath) return os.Rename(oldpath, fname) } + +func syncDir(name string) error { + f, err := os.Open(name) + if err != nil { + return err + } + defer f.Close() + if err := f.Sync(); err != nil { + return err + } + return nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go new file mode 100644 index 00000000..102031bf --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go @@ -0,0 +1,68 @@ +// Copyright (c) 2012, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// +build solaris + +package storage + +import ( + "os" + "syscall" +) + +type unixFileLock struct { + f *os.File +} + +func (fl *unixFileLock) release() error { + if err := setFileLock(fl.f, false); err != nil { + return err + } + return fl.f.Close() +} + +func newFileLock(path string) (fl fileLock, err error) { + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return + } + err = setFileLock(f, true) + if err != nil { + f.Close() + return + } + fl = &unixFileLock{f: f} + return +} + +func setFileLock(f *os.File, lock bool) error { + flock := syscall.Flock_t{ + Type: syscall.F_UNLCK, + Start: 0, + Len: 0, + Whence: 1, + } + if lock { + flock.Type = syscall.F_WRLCK + } + return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &flock) +} + +func rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + +func syncDir(name string) error { + f, err := os.Open(name) + if err != nil { + return err + } + defer f.Close() + if err := f.Sync(); err != nil { + return err + } + return nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go index 395db2d2..d0a604b7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go @@ -4,7 +4,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd package storage @@ -49,3 +49,15 @@ func setFileLock(f *os.File, lock bool) error { func rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } + +func syncDir(name string) error { + f, err := os.Open(name) + if err != nil { + return err + } + defer f.Close() + if err := f.Sync(); err != nil { + return err + } + return nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go index b834b18f..50c3c454 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go @@ -65,3 +65,5 @@ func rename(oldpath, newpath string) error { } return moveFileEx(from, to, _MOVEFILE_REPLACE_EXISTING) } + +func syncDir(name string) error { return nil } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go index d951d975..5a1885e6 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go @@ -9,6 +9,7 @@ package storage import ( "errors" + "fmt" "io" "github.com/syndtr/goleveldb/leveldb/util" @@ -36,7 +37,7 @@ func (t FileType) String() string { case TypeTemp: return "temp" } - return "" + return fmt.Sprintf("", t) } var ( @@ -66,7 +67,7 @@ type Writer interface { Syncer } -// File is the file. +// File is the file. A file instance must be goroutine-safe. type File interface { // Open opens the file for read. Returns os.ErrNotExist error // if the file does not exist. @@ -93,7 +94,7 @@ type File interface { Remove() error } -// Storage is the storage. +// Storage is the storage. A storage instance must be goroutine-safe. type Storage interface { // Lock locks the storage. Any subsequent attempt to call Lock will fail // until the last lock released. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go index 909423e1..27e76d70 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go @@ -20,7 +20,7 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -const typeShift = 3 +const typeShift = 4 var ( tsErrInvalidFile = errors.New("leveldb.testStorage: invalid file for argument") diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table.go index bc20c2f7..87c4e155 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table.go @@ -7,11 +7,12 @@ package leveldb import ( + "fmt" "sort" + "sync" "sync/atomic" "github.com/syndtr/goleveldb/leveldb/cache" - "github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/storage" @@ -19,34 +20,41 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -// table file +// tFile holds basic information about a table. type tFile struct { - file storage.File - seekLeft int32 - size uint64 - min, max iKey + file storage.File + seekLeft int32 + size uint64 + imin, imax iKey } -// test if key is after t -func (t *tFile) isAfter(key []byte, ucmp comparer.BasicComparer) bool { - return key != nil && ucmp.Compare(key, t.max.ukey()) > 0 +// Returns true if given key is after largest key of this table. +func (t *tFile) after(icmp *iComparer, ukey []byte) bool { + return ukey != nil && icmp.uCompare(ukey, t.imax.ukey()) > 0 } -// test if key is before t -func (t *tFile) isBefore(key []byte, ucmp comparer.BasicComparer) bool { - return key != nil && ucmp.Compare(key, t.min.ukey()) < 0 +// Returns true if given key is before smallest key of this table. +func (t *tFile) before(icmp *iComparer, ukey []byte) bool { + return ukey != nil && icmp.uCompare(ukey, t.imin.ukey()) < 0 } -func (t *tFile) incrSeek() int32 { +// Returns true if given key range overlaps with this table key range. +func (t *tFile) overlaps(icmp *iComparer, umin, umax []byte) bool { + return !t.after(icmp, umin) && !t.before(icmp, umax) +} + +// Cosumes one seek and return current seeks left. +func (t *tFile) consumeSeek() int32 { return atomic.AddInt32(&t.seekLeft, -1) } -func newTFile(file storage.File, size uint64, min, max iKey) *tFile { +// Creates new tFile. +func newTableFile(file storage.File, size uint64, imin, imax iKey) *tFile { f := &tFile{ file: file, size: size, - min: min, - max: max, + imin: imin, + imax: imax, } // We arrange to automatically compact this file after @@ -70,33 +78,40 @@ func newTFile(file storage.File, size uint64, min, max iKey) *tFile { return f } -// table files +// tFiles hold multiple tFile. type tFiles []*tFile func (tf tFiles) Len() int { return len(tf) } func (tf tFiles) Swap(i, j int) { tf[i], tf[j] = tf[j], tf[i] } +// Returns true if i smallest key is less than j. +// This used for sort by key in ascending order. func (tf tFiles) lessByKey(icmp *iComparer, i, j int) bool { a, b := tf[i], tf[j] - n := icmp.Compare(a.min, b.min) + n := icmp.Compare(a.imin, b.imin) if n == 0 { return a.file.Num() < b.file.Num() } return n < 0 } +// Returns true if i file number is greater than j. +// This used for sort by file number in descending order. func (tf tFiles) lessByNum(i, j int) bool { return tf[i].file.Num() > tf[j].file.Num() } +// Sorts tables by key in ascending order. func (tf tFiles) sortByKey(icmp *iComparer) { sort.Sort(&tFilesSortByKey{tFiles: tf, icmp: icmp}) } +// Sorts tables by file number in descending order. func (tf tFiles) sortByNum() { sort.Sort(&tFilesSortByNum{tFiles: tf}) } +// Returns sum of all tables size. func (tf tFiles) size() (sum uint64) { for _, t := range tf { sum += t.size @@ -104,96 +119,106 @@ func (tf tFiles) size() (sum uint64) { return sum } -func (tf tFiles) searchMin(key iKey, icmp *iComparer) int { +// Searches smallest index of tables whose its smallest +// key is after or equal with given key. +func (tf tFiles) searchMin(icmp *iComparer, ikey iKey) int { return sort.Search(len(tf), func(i int) bool { - return icmp.Compare(tf[i].min, key) >= 0 + return icmp.Compare(tf[i].imin, ikey) >= 0 }) } -func (tf tFiles) searchMax(key iKey, icmp *iComparer) int { +// Searches smallest index of tables whose its largest +// key is after or equal with given key. +func (tf tFiles) searchMax(icmp *iComparer, ikey iKey) int { return sort.Search(len(tf), func(i int) bool { - return icmp.Compare(tf[i].max, key) >= 0 + return icmp.Compare(tf[i].imax, ikey) >= 0 }) } -func (tf tFiles) isOverlaps(min, max []byte, disjSorted bool, icmp *iComparer) bool { - ucmp := icmp.cmp - - if !disjSorted { - // Need to check against all files +// Returns true if given key range overlaps with one or more +// tables key range. If unsorted is true then binary search will not be used. +func (tf tFiles) overlaps(icmp *iComparer, umin, umax []byte, unsorted bool) bool { + if unsorted { + // Check against all files. for _, t := range tf { - if !t.isAfter(min, ucmp) && !t.isBefore(max, ucmp) { + if t.overlaps(icmp, umin, umax) { return true } } return false } - var idx int - if len(min) > 0 { - // Find the earliest possible internal key for min - idx = tf.searchMax(newIKey(min, kMaxSeq, tSeek), icmp) + i := 0 + if len(umin) > 0 { + // Find the earliest possible internal key for min. + i = tf.searchMax(icmp, newIKey(umin, kMaxSeq, tSeek)) } - - if idx >= len(tf) { - // beginning of range is after all files, so no overlap + if i >= len(tf) { + // Beginning of range is after all files, so no overlap. return false } - return !tf[idx].isBefore(max, ucmp) + return !tf[i].before(icmp, umax) } -func (tf tFiles) getOverlaps(min, max []byte, r *tFiles, disjSorted bool, ucmp comparer.BasicComparer) { +// Returns tables whose its key range overlaps with given key range. +// If overlapped is true then the search will be expanded to tables that +// overlaps with each other. +func (tf tFiles) getOverlaps(dst tFiles, icmp *iComparer, umin, umax []byte, overlapped bool) tFiles { + x := len(dst) for i := 0; i < len(tf); { t := tf[i] - i++ - if t.isAfter(min, ucmp) || t.isBefore(max, ucmp) { - continue - } - - *r = append(*r, t) - if !disjSorted { - // Level-0 files may overlap each other. So check if the newly - // added file has expanded the range. If so, restart search. - if min != nil && ucmp.Compare(t.min.ukey(), min) < 0 { - min = t.min.ukey() - *r = nil - i = 0 - } else if max != nil && ucmp.Compare(t.max.ukey(), max) > 0 { - max = t.max.ukey() - *r = nil - i = 0 + if t.overlaps(icmp, umin, umax) { + if overlapped { + // For overlapped files, check if the newly added file has + // expanded the range. If so, restart search. + if umin != nil && icmp.uCompare(t.imin.ukey(), umin) < 0 { + umin = t.imin.ukey() + dst = dst[:x] + i = 0 + continue + } else if umax != nil && icmp.uCompare(t.imax.ukey(), umax) > 0 { + umax = t.imax.ukey() + dst = dst[:x] + i = 0 + continue + } } + + dst = append(dst, t) } + i++ } - return + return dst } -func (tf tFiles) getRange(icmp *iComparer) (min, max iKey) { +// Returns tables key range. +func (tf tFiles) getRange(icmp *iComparer) (imin, imax iKey) { for i, t := range tf { if i == 0 { - min, max = t.min, t.max + imin, imax = t.imin, t.imax continue } - if icmp.Compare(t.min, min) < 0 { - min = t.min + if icmp.Compare(t.imin, imin) < 0 { + imin = t.imin } - if icmp.Compare(t.max, max) > 0 { - max = t.max + if icmp.Compare(t.imax, imax) > 0 { + imax = t.imax } } return } +// Creates iterator index from tables. func (tf tFiles) newIndexIterator(tops *tOps, icmp *iComparer, slice *util.Range, ro *opt.ReadOptions) iterator.IteratorIndexer { if slice != nil { var start, limit int if slice.Start != nil { - start = tf.searchMax(iKey(slice.Start), icmp) + start = tf.searchMax(icmp, iKey(slice.Start)) } if slice.Limit != nil { - limit = tf.searchMin(iKey(slice.Limit), icmp) + limit = tf.searchMin(icmp, iKey(slice.Limit)) } else { limit = tf.Len() } @@ -208,6 +233,7 @@ func (tf tFiles) newIndexIterator(tops *tOps, icmp *iComparer, slice *util.Range }) } +// Tables iterator index. type tFilesArrayIndexer struct { tFiles tops *tOps @@ -217,7 +243,7 @@ type tFilesArrayIndexer struct { } func (a *tFilesArrayIndexer) Search(key []byte) int { - return a.searchMax(iKey(key), a.icmp) + return a.searchMax(a.icmp, iKey(key)) } func (a *tFilesArrayIndexer) Get(i int) iterator.Iterator { @@ -227,6 +253,7 @@ func (a *tFilesArrayIndexer) Get(i int) iterator.Iterator { return a.tops.newIterator(a.tFiles[i], nil, a.ro) } +// Helper type for sortByKey. type tFilesSortByKey struct { tFiles icmp *iComparer @@ -236,6 +263,7 @@ func (x *tFilesSortByKey) Less(i, j int) bool { return x.lessByKey(x.icmp, i, j) } +// Helper type for sortByNum. type tFilesSortByNum struct { tFiles } @@ -244,19 +272,17 @@ func (x *tFilesSortByNum) Less(i, j int) bool { return x.lessByNum(i, j) } -// table operations +// Table operations. type tOps struct { s *session cache cache.Cache cacheNS cache.Namespace + bpool *util.BufferPool + mu sync.Mutex + closed bool } -func newTableOps(s *session, cacheCap int) *tOps { - c := cache.NewLRUCache(cacheCap) - ns := c.GetNamespace(0) - return &tOps{s, c, ns} -} - +// Creates an empty table and returns table writer. func (t *tOps) create() (*tWriter, error) { file := t.s.getTableFile(t.s.allocFileNum()) fw, err := file.Create() @@ -271,10 +297,11 @@ func (t *tOps) create() (*tWriter, error) { }, nil } +// Builds table from src iterator. func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) { w, err := t.create() if err != nil { - return f, n, err + return } defer func() { @@ -284,7 +311,7 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) { }() for src.Next() { - err = w.add(src.Key(), src.Value()) + err = w.append(src.Key(), src.Value()) if err != nil { return } @@ -299,84 +326,142 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) { return } -func (t *tOps) lookup(f *tFile) (c cache.Object, err error) { +type trWrapper struct { + *table.Reader + t *tOps + ref int +} + +func (w *trWrapper) Release() { + if w.ref != 0 && !w.t.closed { + panic(fmt.Sprintf("BUG: invalid ref %d, refer to issue #72", w.ref)) + } + w.Reader.Release() +} + +type trCacheHandleWrapper struct { + cache.Handle + t *tOps + released bool +} + +func (w *trCacheHandleWrapper) Release() { + w.t.mu.Lock() + defer w.t.mu.Unlock() + + if !w.released { + w.released = true + w.Value().(*trWrapper).ref-- + } + w.Handle.Release() +} + +// Opens table. It returns a cache handle, which should +// be released after use. +func (t *tOps) open(f *tFile) (ch cache.Handle, err error) { + t.mu.Lock() + defer t.mu.Unlock() + num := f.file.Num() - c, ok := t.cacheNS.Get(num, func() (ok bool, value interface{}, charge int, fin cache.SetFin) { + ch = t.cacheNS.Get(num, func() (charge int, value interface{}) { var r storage.Reader r, err = f.file.Open() if err != nil { - return + return 0, nil } - o := t.s.o - - var cacheNS cache.Namespace - if bc := o.GetBlockCache(); bc != nil { - cacheNS = bc.GetNamespace(num) + var bcacheNS cache.Namespace + if bc := t.s.o.GetBlockCache(); bc != nil { + bcacheNS = bc.GetNamespace(num) } - - ok = true - value = table.NewReader(r, int64(f.size), cacheNS, o) - charge = 1 - fin = func() { - r.Close() - } - return + return 1, &trWrapper{table.NewReader(r, int64(f.size), bcacheNS, t.bpool, t.s.o), t, 0} }) - if !ok && err == nil { + if ch == nil && err == nil { err = ErrClosed } + ch.Value().(*trWrapper).ref++ + ch = &trCacheHandleWrapper{ch, t, false} return } -func (t *tOps) get(f *tFile, key []byte, ro *opt.ReadOptions) (rkey, rvalue []byte, err error) { - c, err := t.lookup(f) +// Finds key/value pair whose key is greater than or equal to the +// given key. +func (t *tOps) find(f *tFile, key []byte, ro *opt.ReadOptions) (rkey, rvalue []byte, err error) { + ch, err := t.open(f) if err != nil { return nil, nil, err } - defer c.Release() - return c.Value().(*table.Reader).Find(key, ro) + defer ch.Release() + return ch.Value().(*trWrapper).Find(key, ro) } -func (t *tOps) getApproximateOffset(f *tFile, key []byte) (offset uint64, err error) { - c, err := t.lookup(f) +// Returns approximate offset of the given key. +func (t *tOps) offsetOf(f *tFile, key []byte) (offset uint64, err error) { + ch, err := t.open(f) if err != nil { return } - _offset, err := c.Value().(*table.Reader).GetApproximateOffset(key) - offset = uint64(_offset) - c.Release() - return + defer ch.Release() + offset_, err := ch.Value().(*trWrapper).OffsetOf(key) + return uint64(offset_), err } +// Creates an iterator from the given table. func (t *tOps) newIterator(f *tFile, slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { - c, err := t.lookup(f) + ch, err := t.open(f) if err != nil { return iterator.NewEmptyIterator(err) } - iter := c.Value().(*table.Reader).NewIterator(slice, ro) - iter.SetReleaser(c) + iter := ch.Value().(*trWrapper).NewIterator(slice, ro) + iter.SetReleaser(ch) return iter } +// Removes table from persistent storage. It waits until +// no one use the the table. func (t *tOps) remove(f *tFile) { + t.mu.Lock() + defer t.mu.Unlock() + num := f.file.Num() - t.cacheNS.Delete(num, func(exist bool) { - if err := f.file.Remove(); err != nil { - t.s.logf("table@remove removing @%d %q", num, err) - } else { - t.s.logf("table@remove removed @%d", num) - } - if bc := t.s.o.GetBlockCache(); bc != nil { - bc.GetNamespace(num).Zap(false) + t.cacheNS.Delete(num, func(exist, pending bool) { + if !pending { + if err := f.file.Remove(); err != nil { + t.s.logf("table@remove removing @%d %q", num, err) + } else { + t.s.logf("table@remove removed @%d", num) + } + if bc := t.s.o.GetBlockCache(); bc != nil { + bc.ZapNamespace(num) + } } }) } +// Closes the table ops instance. It will close all tables, +// regadless still used or not. func (t *tOps) close() { - t.cache.Zap(true) + t.mu.Lock() + defer t.mu.Unlock() + + t.closed = true + t.cache.Zap() + t.bpool.Close() } +// Creates new initialized table ops instance. +func newTableOps(s *session, cacheCap int) *tOps { + c := cache.NewLRUCache(cacheCap) + return &tOps{ + s: s, + cache: c, + cacheNS: c.GetNamespace(0), + bpool: util.NewBufferPool(s.o.GetBlockSize() + 5), + } +} + +// tWriter wraps the table writer. It keep track of file descriptor +// and added key range. type tWriter struct { t *tOps @@ -387,7 +472,8 @@ type tWriter struct { first, last []byte } -func (w *tWriter) add(key, value []byte) error { +// Append key/value pair to the table. +func (w *tWriter) append(key, value []byte) error { if w.first == nil { w.first = append([]byte{}, key...) } @@ -395,10 +481,12 @@ func (w *tWriter) add(key, value []byte) error { return w.tw.Append(key, value) } +// Returns true if the table is empty. func (w *tWriter) empty() bool { return w.first == nil } +// Finalizes the table and returns table file. func (w *tWriter) finish() (f *tFile, err error) { err = w.tw.Close() if err != nil { @@ -410,10 +498,11 @@ func (w *tWriter) finish() (f *tFile, err error) { return } w.w.Close() - f = newTFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last)) + f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last)) return } +// Drops the table. func (w *tWriter) drop() { w.w.Close() w.file.Remove() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go index ca598f4f..f3b53c09 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go @@ -40,7 +40,7 @@ var _ = testutil.Defer(func() { data := bw.buf.Bytes() restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:])) return &block{ - cmp: comparer.DefaultComparer, + tr: &Reader{cmp: comparer.DefaultComparer}, data: data, restartsLen: restartsLen, restartsOffset: len(data) - (restartsLen+1)*4, @@ -59,7 +59,7 @@ var _ = testutil.Defer(func() { // Make block. br := Build(kv, restartInterval) // Do testing. - testutil.KeyValueTesting(nil, br, kv.Clone()) + testutil.KeyValueTesting(nil, kv.Clone(), br, nil, nil) } Describe(Text(), Test) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go index a6200884..ab62c44e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go @@ -13,6 +13,7 @@ import ( "io" "sort" "strings" + "sync" "code.google.com/p/snappy-go/snappy" @@ -25,8 +26,9 @@ import ( ) var ( - ErrNotFound = util.ErrNotFound - ErrIterReleased = errors.New("leveldb/table: iterator released") + ErrNotFound = util.ErrNotFound + ErrReaderReleased = errors.New("leveldb/table: reader released") + ErrIterReleased = errors.New("leveldb/table: iterator released") ) func max(x, y int) int { @@ -37,7 +39,7 @@ func max(x, y int) int { } type block struct { - cmp comparer.BasicComparer + tr *Reader data []byte restartsLen int restartsOffset int @@ -46,31 +48,25 @@ type block struct { } func (b *block) seek(rstart, rlimit int, key []byte) (index, offset int, err error) { - n := b.restartsOffset - data := b.data - cmp := b.cmp - index = sort.Search(b.restartsLen-rstart-(b.restartsLen-rlimit), func(i int) bool { - offset := int(binary.LittleEndian.Uint32(data[n+4*(rstart+i):])) - offset += 1 // shared always zero, since this is a restart point - v1, n1 := binary.Uvarint(data[offset:]) // key length - _, n2 := binary.Uvarint(data[offset+n1:]) // value length + offset := int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*(rstart+i):])) + offset += 1 // shared always zero, since this is a restart point + v1, n1 := binary.Uvarint(b.data[offset:]) // key length + _, n2 := binary.Uvarint(b.data[offset+n1:]) // value length m := offset + n1 + n2 - return cmp.Compare(data[m:m+int(v1)], key) > 0 + return b.tr.cmp.Compare(b.data[m:m+int(v1)], key) > 0 }) + rstart - 1 if index < rstart { // The smallest key is greater-than key sought. index = rstart } - offset = int(binary.LittleEndian.Uint32(data[n+4*index:])) + offset = int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*index:])) return } func (b *block) restartIndex(rstart, rlimit, offset int) int { - n := b.restartsOffset - data := b.data return sort.Search(b.restartsLen-rstart-(b.restartsLen-rlimit), func(i int) bool { - return int(binary.LittleEndian.Uint32(data[n+4*(rstart+i):])) > offset + return int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*(rstart+i):])) > offset }) + rstart - 1 } @@ -139,6 +135,12 @@ func (b *block) newIterator(slice *util.Range, inclLimit bool, cache util.Releas return bi } +func (b *block) Release() { + b.tr.bpool.Put(b.data) + b.tr = nil + b.data = nil +} + type dir int const ( @@ -261,7 +263,7 @@ func (i *blockIter) Seek(key []byte) bool { i.dir = dirForward } for i.Next() { - if i.block.cmp.Compare(i.key, key) >= 0 { + if i.block.tr.cmp.Compare(i.key, key) >= 0 { return true } } @@ -437,25 +439,32 @@ func (i *blockIter) Value() []byte { } func (i *blockIter) Release() { - i.prevNode = nil - i.prevKeys = nil - i.key = nil - i.value = nil - i.dir = dirReleased - if i.cache != nil { - i.cache.Release() - i.cache = nil - } - if i.releaser != nil { - i.releaser.Release() - i.releaser = nil + if i.dir != dirReleased { + i.block = nil + i.prevNode = nil + i.prevKeys = nil + i.key = nil + i.value = nil + i.dir = dirReleased + if i.cache != nil { + i.cache.Release() + i.cache = nil + } + if i.releaser != nil { + i.releaser.Release() + i.releaser = nil + } } } func (i *blockIter) SetReleaser(releaser util.Releaser) { - if i.dir > dirReleased { - i.releaser = releaser + if i.dir == dirReleased { + panic(util.ErrReleased) } + if i.releaser != nil && releaser != nil { + panic(util.ErrHasReleaser) + } + i.releaser = releaser } func (i *blockIter) Valid() bool { @@ -467,7 +476,7 @@ func (i *blockIter) Error() error { } type filterBlock struct { - filter filter.Filter + tr *Reader data []byte oOffset int baseLg uint @@ -481,7 +490,7 @@ func (b *filterBlock) contains(offset uint64, key []byte) bool { n := int(binary.LittleEndian.Uint32(o)) m := int(binary.LittleEndian.Uint32(o[4:])) if n < m && m <= b.oOffset { - return b.filter.Contains(b.data[n:m], key) + return b.tr.filter.Contains(b.data[n:m], key) } else if n == m { return false } @@ -489,10 +498,15 @@ func (b *filterBlock) contains(offset uint64, key []byte) bool { return true } +func (b *filterBlock) Release() { + b.tr.bpool.Put(b.data) + b.tr = nil + b.data = nil +} + type indexIter struct { - blockIter - tableReader *Reader - slice *util.Range + *blockIter + slice *util.Range // Options checksum bool fillCache bool @@ -507,27 +521,31 @@ func (i *indexIter) Get() iterator.Iterator { if n == 0 { return iterator.NewEmptyIterator(errors.New("leveldb/table: Reader: invalid table (bad data block handle)")) } + var slice *util.Range if i.slice != nil && (i.blockIter.isFirst() || i.blockIter.isLast()) { slice = i.slice } - return i.tableReader.getDataIter(dataBH, slice, i.checksum, i.fillCache) + return i.blockIter.block.tr.getDataIterErr(dataBH, slice, i.checksum, i.fillCache) } // Reader is a table reader. type Reader struct { + mu sync.RWMutex reader io.ReaderAt cache cache.Namespace err error + bpool *util.BufferPool // Options cmp comparer.Comparer filter filter.Filter checksum bool strictIter bool - dataEnd int64 - indexBlock *block - filterBlock *filterBlock + dataEnd int64 + indexBH, filterBH blockHandle + indexBlock *block + filterBlock *filterBlock } func verifyChecksum(data []byte) bool { @@ -538,12 +556,13 @@ func verifyChecksum(data []byte) bool { } func (r *Reader) readRawBlock(bh blockHandle, checksum bool) ([]byte, error) { - data := make([]byte, bh.length+blockTrailerLen) + data := r.bpool.Get(int(bh.length + blockTrailerLen)) if _, err := r.reader.ReadAt(data, int64(bh.offset)); err != nil && err != io.EOF { return nil, err } if checksum || r.checksum { if !verifyChecksum(data) { + r.bpool.Put(data) return nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)") } } @@ -551,12 +570,18 @@ func (r *Reader) readRawBlock(bh blockHandle, checksum bool) ([]byte, error) { case blockTypeNoCompression: data = data[:bh.length] case blockTypeSnappyCompression: - var err error - data, err = snappy.Decode(nil, data[:bh.length]) + decLen, err := snappy.DecodedLen(data[:bh.length]) + if err != nil { + return nil, err + } + tmp := data + data, err = snappy.Decode(r.bpool.Get(decLen), tmp[:bh.length]) + r.bpool.Put(tmp) if err != nil { return nil, err } default: + r.bpool.Put(data) return nil, fmt.Errorf("leveldb/table: Reader: unknown block compression type: %d", data[bh.length]) } return data, nil @@ -569,7 +594,7 @@ func (r *Reader) readBlock(bh blockHandle, checksum bool) (*block, error) { } restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:])) b := &block{ - cmp: r.cmp, + tr: r, data: data, restartsLen: restartsLen, restartsOffset: len(data) - (restartsLen+1)*4, @@ -578,7 +603,44 @@ func (r *Reader) readBlock(bh blockHandle, checksum bool) (*block, error) { return b, nil } -func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterBlock, error) { +func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*block, util.Releaser, error) { + if r.cache != nil { + var err error + ch := r.cache.Get(bh.offset, func() (charge int, value interface{}) { + if !fillCache { + return 0, nil + } + var b *block + b, err = r.readBlock(bh, checksum) + if err != nil { + return 0, nil + } + return cap(b.data), b + }) + if ch != nil { + b, ok := ch.Value().(*block) + if !ok { + ch.Release() + return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type") + } + if !b.checksum && (r.checksum || checksum) { + if !verifyChecksum(b.data) { + ch.Release() + return nil, nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)") + } + b.checksum = true + } + return b, ch, err + } else if err != nil { + return nil, nil, err + } + } + + b, err := r.readBlock(bh, checksum) + return b, b, err +} + +func (r *Reader) readFilterBlock(bh blockHandle) (*filterBlock, error) { data, err := r.readRawBlock(bh, true) if err != nil { return nil, err @@ -593,7 +655,7 @@ func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterB return nil, errors.New("leveldb/table: Reader: invalid filter block (invalid offset)") } b := &filterBlock{ - filter: filter, + tr: r, data: data, oOffset: oOffset, baseLg: uint(data[n-1]), @@ -602,44 +664,67 @@ func (r *Reader) readFilterBlock(bh blockHandle, filter filter.Filter) (*filterB return b, nil } -func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator { +func (r *Reader) readFilterBlockCached(bh blockHandle, fillCache bool) (*filterBlock, util.Releaser, error) { if r.cache != nil { - // Get/set block cache. var err error - cache, ok := r.cache.Get(dataBH.offset, func() (ok bool, value interface{}, charge int, fin cache.SetFin) { + ch := r.cache.Get(bh.offset, func() (charge int, value interface{}) { if !fillCache { - return + return 0, nil } - var dataBlock *block - dataBlock, err = r.readBlock(dataBH, checksum) - if err == nil { - ok = true - value = dataBlock - charge = int(dataBH.length) + var b *filterBlock + b, err = r.readFilterBlock(bh) + if err != nil { + return 0, nil } - return + return cap(b.data), b }) - if err != nil { - return iterator.NewEmptyIterator(err) - } - if ok { - dataBlock := cache.Value().(*block) - if !dataBlock.checksum && (r.checksum || checksum) { - if !verifyChecksum(dataBlock.data) { - return iterator.NewEmptyIterator(errors.New("leveldb/table: Reader: invalid block (checksum mismatch)")) - } - dataBlock.checksum = true + if ch != nil { + b, ok := ch.Value().(*filterBlock) + if !ok { + ch.Release() + return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type") } - iter := dataBlock.newIterator(slice, false, cache) - return iter + return b, ch, err + } else if err != nil { + return nil, nil, err } } - dataBlock, err := r.readBlock(dataBH, checksum) + + b, err := r.readFilterBlock(bh) + return b, b, err +} + +func (r *Reader) getIndexBlock(fillCache bool) (b *block, rel util.Releaser, err error) { + if r.indexBlock == nil { + return r.readBlockCached(r.indexBH, true, fillCache) + } + return r.indexBlock, util.NoopReleaser{}, nil +} + +func (r *Reader) getFilterBlock(fillCache bool) (*filterBlock, util.Releaser, error) { + if r.filterBlock == nil { + return r.readFilterBlockCached(r.filterBH, fillCache) + } + return r.filterBlock, util.NoopReleaser{}, nil +} + +func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator { + b, rel, err := r.readBlockCached(dataBH, checksum, fillCache) if err != nil { return iterator.NewEmptyIterator(err) } - iter := dataBlock.newIterator(slice, false, nil) - return iter + return b.newIterator(slice, false, rel) +} + +func (r *Reader) getDataIterErr(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator { + r.mu.RLock() + defer r.mu.RUnlock() + + if r.err != nil { + return iterator.NewEmptyIterator(r.err) + } + + return r.getDataIter(dataBH, slice, checksum, fillCache) } // NewIterator creates an iterator from the table. @@ -653,20 +738,26 @@ func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fi // when not used. // // Also read Iterator documentation of the leveldb/iterator package. - func (r *Reader) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator { + r.mu.RLock() + defer r.mu.RUnlock() + if r.err != nil { return iterator.NewEmptyIterator(r.err) } - index := &indexIter{ - blockIter: *r.indexBlock.newIterator(slice, true, nil), - tableReader: r, - slice: slice, - checksum: ro.GetStrict(opt.StrictBlockChecksum), - fillCache: !ro.GetDontFillCache(), + fillCache := !ro.GetDontFillCache() + indexBlock, rel, err := r.getIndexBlock(fillCache) + if err != nil { + return iterator.NewEmptyIterator(err) } - return iterator.NewIndexedIterator(index, r.strictIter || ro.GetStrict(opt.StrictIterator), false) + index := &indexIter{ + blockIter: indexBlock.newIterator(slice, true, rel), + slice: slice, + checksum: ro.GetStrict(opt.StrictBlockChecksum), + fillCache: !ro.GetDontFillCache(), + } + return iterator.NewIndexedIterator(index, r.strictIter || ro.GetStrict(opt.StrictIterator), true) } // Find finds key/value pair whose key is greater than or equal to the @@ -676,12 +767,21 @@ func (r *Reader) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It // The caller should not modify the contents of the returned slice, but // it is safe to modify the contents of the argument after Find returns. func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err error) { + r.mu.RLock() + defer r.mu.RUnlock() + if r.err != nil { err = r.err return } - index := r.indexBlock.newIterator(nil, true, nil) + indexBlock, rel, err := r.getIndexBlock(true) + if err != nil { + return + } + defer rel.Release() + + index := indexBlock.newIterator(nil, true, nil) defer index.Release() if !index.Seek(key) { err = index.Error() @@ -695,9 +795,15 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err err = errors.New("leveldb/table: Reader: invalid table (bad data block handle)") return } - if r.filterBlock != nil && !r.filterBlock.contains(dataBH.offset, key) { - err = ErrNotFound - return + if r.filter != nil { + filterBlock, rel, ferr := r.getFilterBlock(true) + if ferr == nil { + if !filterBlock.contains(dataBH.offset, key) { + rel.Release() + return nil, nil, ErrNotFound + } + rel.Release() + } } data := r.getDataIter(dataBH, nil, ro.GetStrict(opt.StrictBlockChecksum), !ro.GetDontFillCache()) defer data.Release() @@ -708,8 +814,15 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err } return } + // Don't use block buffer, no need to copy the buffer. rkey = data.Key() - value = data.Value() + if r.bpool == nil { + value = data.Value() + } else { + // Use block buffer, and since the buffer will be recycled, the buffer + // need to be copied. + value = append([]byte{}, data.Value()...) + } return } @@ -719,6 +832,9 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err // The caller should not modify the contents of the returned slice, but // it is safe to modify the contents of the argument after Get returns. func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { + r.mu.RLock() + defer r.mu.RUnlock() + if r.err != nil { err = r.err return @@ -732,16 +848,25 @@ func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) return } -// GetApproximateOffset returns approximate offset for the given key. +// OffsetOf returns approximate offset for the given key. // // It is safe to modify the contents of the argument after Get returns. -func (r *Reader) GetApproximateOffset(key []byte) (offset int64, err error) { +func (r *Reader) OffsetOf(key []byte) (offset int64, err error) { + r.mu.RLock() + defer r.mu.RUnlock() + if r.err != nil { err = r.err return } - index := r.indexBlock.newIterator(nil, true, nil) + indexBlock, rel, err := r.readBlockCached(r.indexBH, true, true) + if err != nil { + return + } + defer rel.Release() + + index := indexBlock.newIterator(nil, true, nil) defer index.Release() if index.Seek(key) { dataBH, n := decodeBlockHandle(index.Value()) @@ -759,12 +884,38 @@ func (r *Reader) GetApproximateOffset(key []byte) (offset int64, err error) { return } +// Release implements util.Releaser. +// It also close the file if it is an io.Closer. +func (r *Reader) Release() { + r.mu.Lock() + defer r.mu.Unlock() + + if closer, ok := r.reader.(io.Closer); ok { + closer.Close() + } + if r.indexBlock != nil { + r.indexBlock.Release() + r.indexBlock = nil + } + if r.filterBlock != nil { + r.filterBlock.Release() + r.filterBlock = nil + } + r.reader = nil + r.cache = nil + r.bpool = nil + r.err = ErrReaderReleased +} + // NewReader creates a new initialized table reader for the file. -// The cache is optional and can be nil. -func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options) *Reader { +// The cache and bpool is optional and can be nil. +// +// The returned table reader instance is goroutine-safe. +func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, bpool *util.BufferPool, o *opt.Options) *Reader { r := &Reader{ reader: f, cache: cache, + bpool: bpool, cmp: o.GetComparer(), checksum: o.GetStrict(opt.StrictBlockChecksum), strictIter: o.GetStrict(opt.StrictIterator), @@ -792,16 +943,11 @@ func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options) return r } // Decode the index block handle. - indexBH, n := decodeBlockHandle(footer[n:]) + r.indexBH, n = decodeBlockHandle(footer[n:]) if n == 0 { r.err = errors.New("leveldb/table: Reader: invalid table (bad index block handle)") return r } - // Read index block. - r.indexBlock, r.err = r.readBlock(indexBH, true) - if r.err != nil { - return r - } // Read metaindex block. metaBlock, err := r.readBlock(metaBH, true) if err != nil { @@ -817,32 +963,45 @@ func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, o *opt.Options) continue } fn := key[7:] - var filter filter.Filter if f0 := o.GetFilter(); f0 != nil && f0.Name() == fn { - filter = f0 + r.filter = f0 } else { for _, f0 := range o.GetAltFilters() { if f0.Name() == fn { - filter = f0 + r.filter = f0 break } } } - if filter != nil { + if r.filter != nil { filterBH, n := decodeBlockHandle(metaIter.Value()) if n == 0 { continue } + r.filterBH = filterBH // Update data end. r.dataEnd = int64(filterBH.offset) - filterBlock, err := r.readFilterBlock(filterBH, filter) - if err != nil { - continue - } - r.filterBlock = filterBlock break } } metaIter.Release() + metaBlock.Release() + + // Cache index and filter block locally, since we don't have global cache. + if cache == nil { + r.indexBlock, r.err = r.readBlock(r.indexBH, true) + if r.err != nil { + return r + } + if r.filter != nil { + r.filterBlock, err = r.readFilterBlock(r.filterBH) + if err != nil { + // Don't use filter then. + r.filter = nil + r.filterBH = blockHandle{} + } + } + } + return r } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go index c682f55f..3e6e8583 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go @@ -59,9 +59,9 @@ var _ = testutil.Defer(func() { It("Should be able to approximate offset of a key correctly", func() { Expect(err).ShouldNot(HaveOccurred()) - tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, o) + tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o) CheckOffset := func(key string, expect, threshold int) { - offset, err := tr.GetApproximateOffset([]byte(key)) + offset, err := tr.OffsetOf([]byte(key)) Expect(err).ShouldNot(HaveOccurred()) Expect(offset).Should(BeNumerically("~", expect, threshold), "Offset of key %q", key) } @@ -95,7 +95,7 @@ var _ = testutil.Defer(func() { tw.Close() // Opening the table. - tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, o) + tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o) return tableWrapper{tr} } Test := func(kv *testutil.KeyValue, body func(r *Reader)) func() { @@ -104,14 +104,16 @@ var _ = testutil.Defer(func() { if body != nil { body(db.(tableWrapper).Reader) } - testutil.KeyValueTesting(nil, db, *kv) + testutil.KeyValueTesting(nil, *kv, db, nil, nil) } } - testutil.AllKeyValueTesting(nil, Build) + testutil.AllKeyValueTesting(nil, Build, nil, nil) Describe("with one key per block", Test(testutil.KeyValue_Generate(nil, 9, 1, 10, 512, 512), func(r *Reader) { It("should have correct blocks number", func() { - Expect(r.indexBlock.restartsLen).Should(Equal(9)) + indexBlock, err := r.readBlock(r.indexBH, true) + Expect(err).To(BeNil()) + Expect(indexBlock.restartsLen).Should(Equal(9)) }) })) }) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go index 4fc75b6f..0cead03b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go @@ -16,13 +16,22 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) { +func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, teardown func(DB)) { if rnd == nil { rnd = NewRand() } - if db, ok := p.(Find); ok { - It("Should find all keys with Find", func() { + if p == nil { + BeforeEach(func() { + p = setup(kv) + }) + AfterEach(func() { + teardown(p) + }) + } + + It("Should find all keys with Find", func() { + if db, ok := p.(Find); ok { ShuffledIndex(nil, kv.Len(), 1, func(i int) { key_, key, value := kv.IndexInexact(i) @@ -38,9 +47,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) { Expect(rkey).Should(Equal(key)) Expect(rvalue).Should(Equal(value), "Value for key %q (%q)", key_, key) }) - }) + } + }) - It("Should return error if the key is not present", func() { + It("Should return error if the key is not present", func() { + if db, ok := p.(Find); ok { var key []byte if kv.Len() > 0 { key_, _ := kv.Index(kv.Len() - 1) @@ -49,11 +60,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) { rkey, _, err := db.TestFind(key) Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey) Expect(err).Should(Equal(util.ErrNotFound)) - }) - } + } + }) - if db, ok := p.(Get); ok { - It("Should only find exact key with Get", func() { + It("Should only find exact key with Get", func() { + if db, ok := p.(Get); ok { ShuffledIndex(nil, kv.Len(), 1, func(i int) { key_, key, value := kv.IndexInexact(i) @@ -69,11 +80,11 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) { Expect(err).Should(Equal(util.ErrNotFound)) } }) - }) - } + } + }) - if db, ok := p.(NewIterator); ok { - TestIter := func(r *util.Range, _kv KeyValue) { + TestIter := func(r *util.Range, _kv KeyValue) { + if db, ok := p.(NewIterator); ok { iter := db.TestNewIterator(r) Expect(iter.Error()).ShouldNot(HaveOccurred()) @@ -84,45 +95,48 @@ func KeyValueTesting(rnd *rand.Rand, p DB, kv KeyValue) { DoIteratorTesting(&t) } + } - It("Should iterates and seeks correctly", func(done Done) { - TestIter(nil, kv.Clone()) - done <- true - }, 3.0) + It("Should iterates and seeks correctly", func(done Done) { + TestIter(nil, kv.Clone()) + done <- true + }, 3.0) - RandomIndex(rnd, kv.Len(), kv.Len(), func(i int) { - type slice struct { - r *util.Range - start, limit int - } + RandomIndex(rnd, kv.Len(), kv.Len(), func(i int) { + type slice struct { + r *util.Range + start, limit int + } - key_, _, _ := kv.IndexInexact(i) - for _, x := range []slice{ - {&util.Range{Start: key_, Limit: nil}, i, kv.Len()}, - {&util.Range{Start: nil, Limit: key_}, 0, i}, - } { - It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) { - TestIter(x.r, kv.Slice(x.start, x.limit)) - done <- true - }, 3.0) - } - }) - - RandomRange(rnd, kv.Len(), kv.Len(), func(start, limit int) { - It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) { - r := kv.Range(start, limit) - TestIter(&r, kv.Slice(start, limit)) + key_, _, _ := kv.IndexInexact(i) + for _, x := range []slice{ + {&util.Range{Start: key_, Limit: nil}, i, kv.Len()}, + {&util.Range{Start: nil, Limit: key_}, 0, i}, + } { + It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) { + TestIter(x.r, kv.Slice(x.start, x.limit)) done <- true }, 3.0) - }) - } + } + }) + + RandomRange(rnd, kv.Len(), kv.Len(), func(start, limit int) { + It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) { + r := kv.Range(start, limit) + TestIter(&r, kv.Slice(start, limit)) + done <- true + }, 3.0) + }) } -func AllKeyValueTesting(rnd *rand.Rand, body func(kv KeyValue) DB) { +func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown func(DB)) { Test := func(kv *KeyValue) func() { return func() { - db := body(*kv) - KeyValueTesting(rnd, db, *kv) + var p DB + if body != nil { + p = body(*kv) + } + KeyValueTesting(rnd, *kv, p, setup, teardown) } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go index bd5fc12e..0f8d77a7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go @@ -57,6 +57,7 @@ const ( typeManifest = iota typeJournal typeTable + typeTemp typeCount ) @@ -91,6 +92,8 @@ func flattenType(m StorageMode, t storage.FileType) int { return x + typeJournal case storage.TypeTable: return x + typeTable + case storage.TypeTemp: + return x + typeTemp default: panic("invalid file type") } @@ -107,6 +110,8 @@ func listFlattenType(m StorageMode, t storage.FileType) []int { ret = append(ret, x+typeJournal) case t&storage.TypeTable != 0: ret = append(ret, x+typeTable) + case t&storage.TypeTemp != 0: + ret = append(ret, x+typeTemp) } } switch { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go index c1402fda..111f8730 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go @@ -48,6 +48,7 @@ func (t *testingDB) TestClose() { func newTestingDB(o *opt.Options, ro *opt.ReadOptions, wo *opt.WriteOptions) *testingDB { stor := testutil.NewStorage() db, err := Open(stor, o) + // FIXME: This may be called from outside It, which may cause panic. Expect(err).NotTo(HaveOccurred()) return &testingDB{ DB: db, diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go new file mode 100644 index 00000000..aea39dca --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go @@ -0,0 +1,221 @@ +// Copyright (c) 2014, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package util + +import ( + "fmt" + "sync/atomic" + "time" +) + +type buffer struct { + b []byte + miss int +} + +// BufferPool is a 'buffer pool'. +type BufferPool struct { + pool [6]chan []byte + size [5]uint32 + sizeMiss [5]uint32 + sizeHalf [5]uint32 + baseline [4]int + baselinex0 int + baselinex1 int + baseline0 int + baseline1 int + baseline2 int + close chan struct{} + + get uint32 + put uint32 + half uint32 + less uint32 + equal uint32 + greater uint32 + miss uint32 +} + +func (p *BufferPool) poolNum(n int) int { + if n <= p.baseline0 && n > p.baseline0/2 { + return 0 + } + for i, x := range p.baseline { + if n <= x { + return i + 1 + } + } + return len(p.baseline) + 1 +} + +// Get returns buffer with length of n. +func (p *BufferPool) Get(n int) []byte { + if p == nil { + return make([]byte, n) + } + + atomic.AddUint32(&p.get, 1) + + poolNum := p.poolNum(n) + pool := p.pool[poolNum] + if poolNum == 0 { + // Fast path. + select { + case b := <-pool: + switch { + case cap(b) > n: + if cap(b)-n >= n { + atomic.AddUint32(&p.half, 1) + select { + case pool <- b: + default: + } + return make([]byte, n) + } else { + atomic.AddUint32(&p.less, 1) + return b[:n] + } + case cap(b) == n: + atomic.AddUint32(&p.equal, 1) + return b[:n] + default: + atomic.AddUint32(&p.greater, 1) + } + default: + atomic.AddUint32(&p.miss, 1) + } + + return make([]byte, n, p.baseline0) + } else { + sizePtr := &p.size[poolNum-1] + + select { + case b := <-pool: + switch { + case cap(b) > n: + if cap(b)-n >= n { + atomic.AddUint32(&p.half, 1) + sizeHalfPtr := &p.sizeHalf[poolNum-1] + if atomic.AddUint32(sizeHalfPtr, 1) == 20 { + atomic.StoreUint32(sizePtr, uint32(cap(b)/2)) + atomic.StoreUint32(sizeHalfPtr, 0) + } else { + select { + case pool <- b: + default: + } + } + return make([]byte, n) + } else { + atomic.AddUint32(&p.less, 1) + return b[:n] + } + case cap(b) == n: + atomic.AddUint32(&p.equal, 1) + return b[:n] + default: + atomic.AddUint32(&p.greater, 1) + if uint32(cap(b)) >= atomic.LoadUint32(sizePtr) { + select { + case pool <- b: + default: + } + } + } + default: + atomic.AddUint32(&p.miss, 1) + } + + if size := atomic.LoadUint32(sizePtr); uint32(n) > size { + if size == 0 { + atomic.CompareAndSwapUint32(sizePtr, 0, uint32(n)) + } else { + sizeMissPtr := &p.sizeMiss[poolNum-1] + if atomic.AddUint32(sizeMissPtr, 1) == 20 { + atomic.StoreUint32(sizePtr, uint32(n)) + atomic.StoreUint32(sizeMissPtr, 0) + } + } + return make([]byte, n) + } else { + return make([]byte, n, size) + } + } +} + +// Put adds given buffer to the pool. +func (p *BufferPool) Put(b []byte) { + if p == nil { + return + } + + atomic.AddUint32(&p.put, 1) + + pool := p.pool[p.poolNum(cap(b))] + select { + case pool <- b: + default: + } + +} + +func (p *BufferPool) Close() { + if p == nil { + return + } + + select { + case p.close <- struct{}{}: + default: + } +} + +func (p *BufferPool) String() string { + if p == nil { + return "" + } + + return fmt.Sprintf("BufferPool{B·%d Z·%v Zm·%v Zh·%v G·%d P·%d H·%d <·%d =·%d >·%d M·%d}", + p.baseline0, p.size, p.sizeMiss, p.sizeHalf, p.get, p.put, p.half, p.less, p.equal, p.greater, p.miss) +} + +func (p *BufferPool) drain() { + ticker := time.NewTicker(2 * time.Second) + for { + select { + case <-ticker.C: + for _, ch := range p.pool { + select { + case <-ch: + default: + } + } + case <-p.close: + for _, ch := range p.pool { + close(ch) + } + return + } + } +} + +// NewBufferPool creates a new initialized 'buffer pool'. +func NewBufferPool(baseline int) *BufferPool { + if baseline <= 0 { + panic("baseline can't be <= 0") + } + p := &BufferPool{ + baseline0: baseline, + baseline: [...]int{baseline / 4, baseline / 2, baseline * 2, baseline * 4}, + close: make(chan struct{}, 1), + } + for i, cap := range []int{2, 2, 4, 4, 2, 1} { + p.pool[i] = make(chan []byte, cap) + } + go p.drain() + return p +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool.go new file mode 100644 index 00000000..1f7fdd41 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool.go @@ -0,0 +1,21 @@ +// Copyright (c) 2014, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// +build go1.3 + +package util + +import ( + "sync" +) + +type Pool struct { + sync.Pool +} + +func NewPool(cap int) *Pool { + return &Pool{} +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool_legacy.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool_legacy.go new file mode 100644 index 00000000..27b8d03b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/pool_legacy.go @@ -0,0 +1,33 @@ +// Copyright (c) 2014, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// +build !go1.3 + +package util + +type Pool struct { + pool chan interface{} +} + +func (p *Pool) Get() interface{} { + select { + case x := <-p.pool: + return x + default: + return nil + } +} + +func (p *Pool) Put(x interface{}) { + select { + case p.pool <- x: + default: + } +} + +func NewPool(cap int) *Pool { + return &Pool{pool: make(chan interface{}, cap)} +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/range.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/range.go index da058312..85159583 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/range.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/range.go @@ -14,3 +14,19 @@ type Range struct { // Limit of the key range, not include in the range. Limit []byte } + +// BytesPrefix returns key range that satisfy the given prefix. +// This only applicable for the standard 'bytes comparer'. +func BytesPrefix(prefix []byte) *Range { + var limit []byte + for i := len(prefix) - 1; i >= 0; i-- { + c := prefix[i] + if c < 0xff { + limit = make([]byte, i+1) + copy(limit, prefix) + limit[i] = c + 1 + break + } + } + return &Range{prefix, limit} +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go index 229c7d41..f690e484 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go @@ -12,7 +12,9 @@ import ( ) var ( - ErrNotFound = errors.New("leveldb: not found") + ErrNotFound = errors.New("leveldb: not found") + ErrReleased = errors.New("leveldb: resource already relesed") + ErrHasReleaser = errors.New("leveldb: releaser already defined") ) // Releaser is the interface that wraps the basic Release method. @@ -27,23 +29,46 @@ type ReleaseSetter interface { // SetReleaser associates the given releaser to the resources. The // releaser will be called once coresponding resources released. // Calling SetReleaser with nil will clear the releaser. + // + // This will panic if a releaser already present or coresponding + // resource is already released. Releaser should be cleared first + // before assigned a new one. SetReleaser(releaser Releaser) } // BasicReleaser provides basic implementation of Releaser and ReleaseSetter. type BasicReleaser struct { releaser Releaser + released bool +} + +// Released returns whether Release method already called. +func (r *BasicReleaser) Released() bool { + return r.released } // Release implements Releaser.Release. func (r *BasicReleaser) Release() { - if r.releaser != nil { - r.releaser.Release() - r.releaser = nil + if !r.released { + if r.releaser != nil { + r.releaser.Release() + r.releaser = nil + } + r.released = true } } // SetReleaser implements ReleaseSetter.SetReleaser. func (r *BasicReleaser) SetReleaser(releaser Releaser) { + if r.released { + panic(ErrReleased) + } + if r.releaser != nil && releaser != nil { + panic(ErrHasReleaser) + } r.releaser = releaser } + +type NoopReleaser struct{} + +func (NoopReleaser) Release() {} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go index 52ba2907..81fd9ee0 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go @@ -40,8 +40,8 @@ type version struct { tables [kNumLevels]tFiles // Level that should be compacted next and its compaction score. - // Score < 1 means compaction is not strictly needed. These fields - // are initialized by ComputeCompaction() + // Score < 1 means compaction is not strictly needed. These fields + // are initialized by computeCompaction() cLevel int cScore float64 @@ -60,8 +60,6 @@ func (v *version) release_NB() { panic("negative version ref") } - s := v.s - tables := make(map[uint64]bool) for _, tt := range v.next.tables { for _, t := range tt { @@ -74,7 +72,7 @@ func (v *version) release_NB() { for _, t := range tt { num := t.file.Num() if _, ok := tables[num]; !ok { - s.tops.remove(t) + v.s.tops.remove(t) } } } @@ -89,109 +87,142 @@ func (v *version) release() { v.s.vmu.Unlock() } -func (v *version) get(key iKey, ro *opt.ReadOptions) (value []byte, cstate bool, err error) { - s := v.s - icmp := s.cmp - ucmp := icmp.cmp +func (v *version) walkOverlapping(ikey iKey, f func(level int, t *tFile) bool, lf func(level int) bool) { + ukey := ikey.ukey() - ukey := key.ukey() - - var tset *tSet - tseek := true - - // We can search level-by-level since entries never hop across - // levels. Therefore we are guaranteed that if we find data - // in an smaller level, later levels are irrelevant. - for level, ts := range v.tables { - if len(ts) == 0 { + // Walk tables level-by-level. + for level, tables := range v.tables { + if len(tables) == 0 { continue } if level == 0 { // Level-0 files may overlap each other. Find all files that - // overlap user_key and process them in order from newest to - var tmp tFiles - for _, t := range ts { - if ucmp.Compare(ukey, t.min.ukey()) >= 0 && - ucmp.Compare(ukey, t.max.ukey()) <= 0 { - tmp = append(tmp, t) + // overlap ukey. + for _, t := range tables { + if t.overlaps(v.s.icmp, ukey, ukey) { + if !f(level, t) { + return + } } } - - if len(tmp) == 0 { - continue - } - - tmp.sortByNum() - ts = tmp } else { - i := ts.searchMax(key, icmp) - if i >= len(ts) || ucmp.Compare(ukey, ts[i].min.ukey()) < 0 { - continue + if i := tables.searchMax(v.s.icmp, ikey); i < len(tables) { + t := tables[i] + if v.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 { + if !f(level, t) { + return + } + } } - - ts = ts[i : i+1] } - for _, t := range ts { - if tseek { - if tset == nil { - tset = &tSet{level, t} - } else if tset.table.incrSeek() <= 0 { - cstate = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset)) - tseek = false - } - } - - var _rkey, rval []byte - _rkey, rval, err = s.tops.get(t, key, ro) - if err == ErrNotFound { - continue - } else if err != nil { - return - } - - rkey := iKey(_rkey) - if _, t, ok := rkey.parseNum(); ok { - if ucmp.Compare(ukey, rkey.ukey()) == 0 { - switch t { - case tVal: - value = rval - case tDel: - err = ErrNotFound - default: - panic("not reached") - } - return value, cstate, err - } - } else { - err = errors.New("leveldb: internal key corrupted") - return - } + if lf != nil && !lf(level) { + return } } +} + +func (v *version) get(ikey iKey, ro *opt.ReadOptions) (value []byte, tcomp bool, err error) { + ukey := ikey.ukey() + + var ( + tset *tSet + tseek bool + + l0found bool + l0seq uint64 + l0vt vType + l0val []byte + ) err = ErrNotFound + + // Since entries never hope across level, finding key/value + // in smaller level make later levels irrelevant. + v.walkOverlapping(ikey, func(level int, t *tFile) bool { + if !tseek { + if tset == nil { + tset = &tSet{level, t} + } else if tset.table.consumeSeek() <= 0 { + tseek = true + tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset)) + } + } + + ikey__, val_, err_ := v.s.tops.find(t, ikey, ro) + switch err_ { + case nil: + case ErrNotFound: + return true + default: + err = err_ + return false + } + + ikey_ := iKey(ikey__) + if seq, vt, ok := ikey_.parseNum(); ok { + if v.s.icmp.uCompare(ukey, ikey_.ukey()) != 0 { + return true + } + + if level == 0 { + if seq >= l0seq { + l0found = true + l0seq = seq + l0vt = vt + l0val = val_ + } + } else { + switch vt { + case tVal: + value = val_ + err = nil + case tDel: + default: + panic("leveldb: invalid internal key type") + } + return false + } + } else { + err = errors.New("leveldb: internal key corrupted") + return false + } + + return true + }, func(level int) bool { + if l0found { + switch l0vt { + case tVal: + value = l0val + err = nil + case tDel: + default: + panic("leveldb: invalid internal key type") + } + return false + } + + return true + }) + return } func (v *version) getIterators(slice *util.Range, ro *opt.ReadOptions) (its []iterator.Iterator) { - s := v.s - icmp := s.cmp - // Merge all level zero files together since they may overlap for _, t := range v.tables[0] { - it := s.tops.newIterator(t, slice, ro) + it := v.s.tops.newIterator(t, slice, ro) its = append(its, it) } - strict := s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) - for _, tt := range v.tables[1:] { - if len(tt) == 0 { + strict := v.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) + for _, tables := range v.tables[1:] { + if len(tables) == 0 { continue } - it := iterator.NewIndexedIterator(tt.newIndexIterator(s.tops, icmp, slice, ro), strict, true) + it := iterator.NewIndexedIterator(tables.newIndexIterator(v.s.tops, v.s.icmp, slice, ro), strict, true) its = append(its, it) } @@ -221,28 +252,25 @@ func (v *version) tLen(level int) int { return len(v.tables[level]) } -func (v *version) getApproximateOffset(key iKey) (n uint64, err error) { - icmp := v.s.cmp - tops := v.s.tops - - for level, tt := range v.tables { - for _, t := range tt { - if icmp.Compare(t.max, key) <= 0 { - // Entire file is before "key", so just add the file size +func (v *version) offsetOf(ikey iKey) (n uint64, err error) { + for level, tables := range v.tables { + for _, t := range tables { + if v.s.icmp.Compare(t.imax, ikey) <= 0 { + // Entire file is before "ikey", so just add the file size n += t.size - } else if icmp.Compare(t.min, key) > 0 { - // Entire file is after "key", so ignore + } else if v.s.icmp.Compare(t.imin, ikey) > 0 { + // Entire file is after "ikey", so ignore if level > 0 { // Files other than level 0 are sorted by meta->min, so // no further files in this level will contain data for - // "key". + // "ikey". break } } else { - // "key" falls in the range for this table. Add the - // approximate offset of "key" within the table. + // "ikey" falls in the range for this table. Add the + // approximate offset of "ikey" within the table. var nn uint64 - nn, err = tops.getApproximateOffset(t, key) + nn, err = v.s.tops.offsetOf(t, ikey) if err != nil { return 0, err } @@ -254,18 +282,15 @@ func (v *version) getApproximateOffset(key iKey) (n uint64, err error) { return } -func (v *version) pickLevel(min, max []byte) (level int) { - icmp := v.s.cmp - ucmp := icmp.cmp - - if !v.tables[0].isOverlaps(min, max, false, icmp) { - var r tFiles +func (v *version) pickLevel(umin, umax []byte) (level int) { + if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) { + var overlaps tFiles for ; level < kMaxMemCompactLevel; level++ { - if v.tables[level+1].isOverlaps(min, max, true, icmp) { + if v.tables[level+1].overlaps(v.s.icmp, umin, umax, false) { break } - v.tables[level+2].getOverlaps(min, max, &r, true, ucmp) - if r.size() > kMaxGrandParentOverlapBytes { + overlaps = v.tables[level+2].getOverlaps(overlaps, v.s.icmp, umin, umax, false) + if overlaps.size() > kMaxGrandParentOverlapBytes { break } } @@ -279,7 +304,7 @@ func (v *version) computeCompaction() { var bestLevel int = -1 var bestScore float64 = -1 - for level, ff := range v.tables { + for level, tables := range v.tables { var score float64 if level == 0 { // We treat level-0 specially by bounding the number of files @@ -293,9 +318,9 @@ func (v *version) computeCompaction() { // file size is small (perhaps because of a small write-buffer // setting, or very high compression ratios, or lots of // overwrites/deletions). - score = float64(len(ff)) / kL0_CompactionTrigger + score = float64(len(tables)) / kL0_CompactionTrigger } else { - score = float64(ff.size()) / levelMaxSize[level] + score = float64(tables.size()) / levelMaxSize[level] } if score > bestScore { @@ -321,57 +346,51 @@ type versionStaging struct { } func (p *versionStaging) commit(r *sessionRecord) { - btt := p.base.tables + // Deleted tables. + for _, r := range r.deletedTables { + tm := &(p.tables[r.level]) - // deleted tables - for _, tr := range r.deletedTables { - tm := &(p.tables[tr.level]) - - bt := btt[tr.level] - if len(bt) > 0 { + if len(p.base.tables[r.level]) > 0 { if tm.deleted == nil { tm.deleted = make(map[uint64]struct{}) } - tm.deleted[tr.num] = struct{}{} + tm.deleted[r.num] = struct{}{} } if tm.added != nil { - delete(tm.added, tr.num) + delete(tm.added, r.num) } } - // new tables - for _, tr := range r.addedTables { - tm := &(p.tables[tr.level]) + // New tables. + for _, r := range r.addedTables { + tm := &(p.tables[r.level]) if tm.added == nil { tm.added = make(map[uint64]ntRecord) } - tm.added[tr.num] = tr + tm.added[r.num] = r if tm.deleted != nil { - delete(tm.deleted, tr.num) + delete(tm.deleted, r.num) } } } func (p *versionStaging) finish() *version { - s := p.base.s - btt := p.base.tables - - // build new version - nv := &version{s: s} + // Build new version. + nv := &version{s: p.base.s} for level, tm := range p.tables { - bt := btt[level] + btables := p.base.tables[level] - n := len(bt) + len(tm.added) - len(tm.deleted) + n := len(btables) + len(tm.added) - len(tm.deleted) if n < 0 { n = 0 } nt := make(tFiles, 0, n) - // base tables - for _, t := range bt { + // Base tables. + for _, t := range btables { if _, ok := tm.deleted[t.file.Num()]; ok { continue } @@ -381,17 +400,21 @@ func (p *versionStaging) finish() *version { nt = append(nt, t) } - // new tables - for _, tr := range tm.added { - nt = append(nt, tr.makeFile(s)) + // New tables. + for _, r := range tm.added { + nt = append(nt, r.makeFile(p.base.s)) } - // sort tables - nt.sortByKey(s.cmp) + // Sort tables. + if level == 0 { + nt.sortByNum() + } else { + nt.sortByKey(p.base.s.icmp) + } nv.tables[level] = nt } - // compute compaction score for new version + // Compute compaction score for new version. nv.computeCompaction() return nv diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/LICENSE new file mode 100644 index 00000000..968b4538 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2013 Vaughan Newton + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/README.md new file mode 100644 index 00000000..d5cd4e74 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/README.md @@ -0,0 +1,70 @@ +go-ini +====== + +INI parsing library for Go (golang). + +View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini). + +Usage +----- + +Parse an INI file: + +```go +import "github.com/vaughan0/go-ini" + +file, err := ini.LoadFile("myfile.ini") +``` + +Get data from the parsed file: + +```go +name, ok := file.Get("person", "name") +if !ok { + panic("'name' variable missing from 'person' section") +} +``` + +Iterate through values in a section: + +```go +for key, value := range file["mysection"] { + fmt.Printf("%s => %s\n", key, value) +} +``` + +Iterate through sections in a file: + +```go +for name, section := range file { + fmt.Printf("Section name: %s\n", name) +} +``` + +File Format +----------- + +INI files are parsed by go-ini line-by-line. Each line may be one of the following: + + * A section definition: [section-name] + * A property: key = value + * A comment: #blahblah _or_ ;blahblah + * Blank. The line will be ignored. + +Properties defined before any section headers are placed in the default section, which has +the empty string as it's key. + +Example: + +```ini +# I am a comment +; So am I! + +[apples] +colour = red or green +shape = applish + +[oranges] +shape = square +colour = blue +``` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini.go b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini.go new file mode 100644 index 00000000..81aeb32f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini.go @@ -0,0 +1,123 @@ +// Package ini provides functions for parsing INI configuration files. +package ini + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "strings" +) + +var ( + sectionRegex = regexp.MustCompile(`^\[(.*)\]$`) + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) +) + +// ErrSyntax is returned when there is a syntax error in an INI file. +type ErrSyntax struct { + Line int + Source string // The contents of the erroneous line, without leading or trailing whitespace +} + +func (e ErrSyntax) Error() string { + return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source) +} + +// A File represents a parsed INI file. +type File map[string]Section + +// A Section represents a single section of an INI file. +type Section map[string]string + +// Returns a named Section. A Section will be created if one does not already exist for the given name. +func (f File) Section(name string) Section { + section := f[name] + if section == nil { + section = make(Section) + f[name] = section + } + return section +} + +// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup. +func (f File) Get(section, key string) (value string, ok bool) { + if s := f[section]; s != nil { + value, ok = s[key] + } + return +} + +// Loads INI data from a reader and stores the data in the File. +func (f File) Load(in io.Reader) (err error) { + bufin, ok := in.(*bufio.Reader) + if !ok { + bufin = bufio.NewReader(in) + } + return parseFile(bufin, f) +} + +// Loads INI data from a named file and stores the data in the File. +func (f File) LoadFile(file string) (err error) { + in, err := os.Open(file) + if err != nil { + return + } + defer in.Close() + return f.Load(in) +} + +func parseFile(in *bufio.Reader, file File) (err error) { + section := "" + lineNum := 0 + for done := false; !done; { + var line string + if line, err = in.ReadString('\n'); err != nil { + if err == io.EOF { + done = true + } else { + return + } + } + lineNum++ + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val := groups[1], groups[2] + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + file.Section(section)[key] = val + } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil { + name := strings.TrimSpace(groups[1]) + section = name + // Create the section if it does not exist + file.Section(section) + } else { + return ErrSyntax{lineNum, line} + } + + } + return nil +} + +// Loads and returns a File from a reader. +func Load(in io.Reader) (File, error) { + file := make(File) + err := file.Load(in) + return file, err +} + +// Loads and returns an INI File from a file on disk. +func LoadFile(filename string) (File, error) { + file := make(File) + err := file.LoadFile(filename) + return file, err +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_linux_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_linux_test.go new file mode 100644 index 00000000..38a6f000 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_linux_test.go @@ -0,0 +1,43 @@ +package ini + +import ( + "reflect" + "syscall" + "testing" +) + +func TestLoadFile(t *testing.T) { + originalOpenFiles := numFilesOpen(t) + + file, err := LoadFile("test.ini") + if err != nil { + t.Fatal(err) + } + + if originalOpenFiles != numFilesOpen(t) { + t.Error("test.ini not closed") + } + + if !reflect.DeepEqual(file, File{"default": {"stuff": "things"}}) { + t.Error("file not read correctly") + } +} + +func numFilesOpen(t *testing.T) (num uint64) { + var rlimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) + if err != nil { + t.Fatal(err) + } + maxFds := int(rlimit.Cur) + + var stat syscall.Stat_t + for i := 0; i < maxFds; i++ { + if syscall.Fstat(i, &stat) == nil { + num++ + } else { + return + } + } + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_test.go new file mode 100644 index 00000000..06a4d05e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/ini_test.go @@ -0,0 +1,89 @@ +package ini + +import ( + "reflect" + "strings" + "testing" +) + +func TestLoad(t *testing.T) { + src := ` + # Comments are ignored + + herp = derp + + [foo] + hello=world + whitespace should = not matter + ; sneaky semicolon-style comment + multiple = equals = signs + + [bar] + this = that` + + file, err := Load(strings.NewReader(src)) + if err != nil { + t.Fatal(err) + } + check := func(section, key, expect string) { + if value, _ := file.Get(section, key); value != expect { + t.Errorf("Get(%q, %q): expected %q, got %q", section, key, expect, value) + } + } + + check("", "herp", "derp") + check("foo", "hello", "world") + check("foo", "whitespace should", "not matter") + check("foo", "multiple", "equals = signs") + check("bar", "this", "that") +} + +func TestSyntaxError(t *testing.T) { + src := ` + # Line 2 + [foo] + bar = baz + # Here's an error on line 6: + wut? + herp = derp` + _, err := Load(strings.NewReader(src)) + t.Logf("%T: %v", err, err) + if err == nil { + t.Fatal("expected an error, got nil") + } + syntaxErr, ok := err.(ErrSyntax) + if !ok { + t.Fatal("expected an error of type ErrSyntax") + } + if syntaxErr.Line != 6 { + t.Fatal("incorrect line number") + } + if syntaxErr.Source != "wut?" { + t.Fatal("incorrect source") + } +} + +func TestDefinedSectionBehaviour(t *testing.T) { + check := func(src string, expect File) { + file, err := Load(strings.NewReader(src)) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(file, expect) { + t.Errorf("expected %v, got %v", expect, file) + } + } + // No sections for an empty file + check("", File{}) + // Default section only if there are actually values for it + check("foo=bar", File{"": {"foo": "bar"}}) + // User-defined sections should always be present, even if empty + check("[a]\n[b]\nfoo=bar", File{ + "a": {}, + "b": {"foo": "bar"}, + }) + check("foo=bar\n[a]\nthis=that", File{ + "": {"foo": "bar"}, + "a": {"this": "that"}, + }) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/test.ini b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/test.ini new file mode 100644 index 00000000..d13c999e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/vaughan0/go-ini/test.ini @@ -0,0 +1,2 @@ +[default] +stuff = things diff --git a/src/github.com/smira/aptly/aptly/interfaces.go b/src/github.com/smira/aptly/aptly/interfaces.go index c79b6e15..b6375784 100644 --- a/src/github.com/smira/aptly/aptly/interfaces.go +++ b/src/github.com/smira/aptly/aptly/interfaces.go @@ -90,6 +90,8 @@ type Downloader interface { // Shutdown stops downloader after current tasks are finished, // but doesn't process rest of queue Shutdown() + // Abort stops downloader without waiting for shutdown + Abort() // GetProgress returns Progress object GetProgress() Progress } diff --git a/src/github.com/smira/aptly/aptly/version.go b/src/github.com/smira/aptly/aptly/version.go index a8eb503d..f28e1044 100644 --- a/src/github.com/smira/aptly/aptly/version.go +++ b/src/github.com/smira/aptly/aptly/version.go @@ -1,7 +1,7 @@ package aptly // Version of aptly -const Version = "0.7.1" +const Version = "0.8" // Enable debugging features? const EnableDebug = false diff --git a/src/github.com/smira/aptly/cmd/cmd.go b/src/github.com/smira/aptly/cmd/cmd.go index 0246d493..0b635297 100644 --- a/src/github.com/smira/aptly/cmd/cmd.go +++ b/src/github.com/smira/aptly/cmd/cmd.go @@ -34,6 +34,18 @@ func ListPackagesRefList(reflist *deb.PackageRefList) (err error) { return } +// LookupOption checks boolean flag with default (usually config) and command-line +// setting +func LookupOption(defaultValue bool, flags *flag.FlagSet, name string) (result bool) { + result = defaultValue + + if flags.IsSet(name) { + result = flags.Lookup(name).Value.Get().(bool) + } + + return +} + // RootCommand creates root command in command tree func RootCommand() *commander.Command { cmd := &commander.Command{ @@ -46,9 +58,9 @@ upgrade individual packages, take snapshots and publish them back as Debian repositories. aptly's goal is to establish repeatability and controlled changes -in a package-centric environment. aptly allows to fix a set of packages +in a package-centric environment. aptly allows one to fix a set of packages in a repository, so that package installation and upgrade becomes -deterministic. At the same time aptly allows to perform controlled, +deterministic. At the same time aptly allows one to perform controlled, fine-grained changes in repository contents to transition your package environment to new version.`, Flag: *flag.NewFlagSet("aptly", flag.ExitOnError), @@ -59,8 +71,11 @@ package environment to new version.`, makeCmdRepo(), makeCmdServe(), makeCmdSnapshot(), + // Disabled on no docs + //makeCmdTask(), makeCmdPublish(), makeCmdVersion(), + makeCmdPackage(), }, } diff --git a/src/github.com/smira/aptly/cmd/context.go b/src/github.com/smira/aptly/cmd/context.go index 58d322af..f8472f4f 100644 --- a/src/github.com/smira/aptly/cmd/context.go +++ b/src/github.com/smira/aptly/cmd/context.go @@ -22,8 +22,8 @@ import ( // AptlyContext is a common context shared by all commands type AptlyContext struct { - flags *flag.FlagSet - configLoaded bool + flags, globalFlags *flag.FlagSet + configLoaded bool progress aptly.Progress downloader aptly.Downloader @@ -65,7 +65,7 @@ func (context *AptlyContext) Config() *utils.ConfigStructure { if !context.configLoaded { var err error - configLocation := context.flags.Lookup("config").Value.String() + configLocation := context.globalFlags.Lookup("config").Value.String() if configLocation != "" { err = utils.LoadConfig(configLocation, &utils.Config) @@ -104,16 +104,16 @@ func (context *AptlyContext) Config() *utils.ConfigStructure { func (context *AptlyContext) DependencyOptions() int { if context.dependencyOptions == -1 { context.dependencyOptions = 0 - if context.Config().DepFollowSuggests || context.flags.Lookup("dep-follow-suggests").Value.Get().(bool) { + if LookupOption(context.Config().DepFollowSuggests, context.globalFlags, "dep-follow-suggests") { context.dependencyOptions |= deb.DepFollowSuggests } - if context.Config().DepFollowRecommends || context.flags.Lookup("dep-follow-recommends").Value.Get().(bool) { + if LookupOption(context.Config().DepFollowRecommends, context.globalFlags, "dep-follow-recommends") { context.dependencyOptions |= deb.DepFollowRecommends } - if context.Config().DepFollowAllVariants || context.flags.Lookup("dep-follow-all-variants").Value.Get().(bool) { + if LookupOption(context.Config().DepFollowAllVariants, context.globalFlags, "dep-follow-all-variants") { context.dependencyOptions |= deb.DepFollowAllVariants } - if context.Config().DepFollowSource || context.flags.Lookup("dep-follow-source").Value.Get().(bool) { + if LookupOption(context.Config().DepFollowSource, context.globalFlags, "dep-follow-source") { context.dependencyOptions |= deb.DepFollowSource } } @@ -125,7 +125,7 @@ func (context *AptlyContext) DependencyOptions() int { func (context *AptlyContext) ArchitecturesList() []string { if context.architecturesList == nil { context.architecturesList = context.Config().Architectures - optionArchitectures := context.flags.Lookup("architectures").Value.String() + optionArchitectures := context.globalFlags.Lookup("architectures").Value.String() if optionArchitectures != "" { context.architecturesList = strings.Split(optionArchitectures, ",") } @@ -181,6 +181,36 @@ func (context *AptlyContext) Database() (database.Storage, error) { return context.database, nil } +// CloseDatabase closes the db temporarily +func (context *AptlyContext) CloseDatabase() error { + if context.database == nil { + return nil + } + + return context.database.Close() +} + +// ReOpenDatabase reopens the db after close +func (context *AptlyContext) ReOpenDatabase() error { + if context.database == nil { + return nil + } + + const MaxTries = 10 + const Delay = 10 * time.Second + + for try := 0; try < MaxTries; try++ { + err := context.database.ReOpen() + if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 { + return err + } + context.Progress().Printf("Unable to reopen database, sleeping %s\n", Delay) + <-time.After(Delay) + } + + return fmt.Errorf("unable to reopen the DB, maximum number of retries reached") +} + // CollectionFactory builds factory producing all kinds of collections func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory { if context.collectionFactory == nil { @@ -203,7 +233,7 @@ func (context *AptlyContext) PackagePool() aptly.PackagePool { return context.packagePool } -// PublishedStorage returns instance of PublishedStorage +// GetPublishedStorage returns instance of PublishedStorage func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage { publishedStorage, ok := context.publishedStorages[name] if !ok { @@ -217,7 +247,8 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto var err error publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey, - params.Region, params.Bucket, params.ACL, params.Prefix) + params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass, + params.EncryptionMethod, params.PlusWorkaround) if err != nil { Fatal(err) } @@ -230,6 +261,11 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto return publishedStorage } +// UpdateFlags sets internal copy of flags in the context +func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) { + context.flags = flags +} + // ShutdownContext shuts context down func ShutdownContext() { if aptly.EnableDebug { @@ -250,12 +286,27 @@ func ShutdownContext() { } if context.database != nil { context.database.Close() + context.database = nil } if context.downloader != nil { - context.downloader.Shutdown() + context.downloader.Abort() + context.downloader = nil } if context.progress != nil { context.progress.Shutdown() + context.progress = nil + } +} + +// CleanupContext does partial shutdown of context +func CleanupContext() { + if context.downloader != nil { + context.downloader.Shutdown() + context.downloader = nil + } + if context.progress != nil { + context.progress.Shutdown() + context.progress = nil } } @@ -263,8 +314,13 @@ func ShutdownContext() { func InitContext(flags *flag.FlagSet) error { var err error + if context != nil { + panic("context already initialized") + } + context = &AptlyContext{ flags: flags, + globalFlags: flags, dependencyOptions: -1, publishedStorages: map[string]aptly.PublishedStorage{}, } diff --git a/src/github.com/smira/aptly/cmd/mirror.go b/src/github.com/smira/aptly/cmd/mirror.go index 500ba189..e4ce494a 100644 --- a/src/github.com/smira/aptly/cmd/mirror.go +++ b/src/github.com/smira/aptly/cmd/mirror.go @@ -8,7 +8,7 @@ import ( ) func getVerifier(flags *flag.FlagSet) (utils.Verifier, error) { - if context.Config().GpgDisableVerify || flags.Lookup("ignore-signatures").Value.Get().(bool) { + if LookupOption(context.Config().GpgDisableVerify, flags, "ignore-signatures") { return nil, nil } @@ -56,6 +56,7 @@ func makeCmdMirror() *commander.Command { makeCmdMirrorUpdate(), makeCmdMirrorRename(), makeCmdMirrorEdit(), + makeCmdMirrorSearch(), }, } } diff --git a/src/github.com/smira/aptly/cmd/mirror_create.go b/src/github.com/smira/aptly/cmd/mirror_create.go index b902b26c..27a7dcc0 100644 --- a/src/github.com/smira/aptly/cmd/mirror_create.go +++ b/src/github.com/smira/aptly/cmd/mirror_create.go @@ -16,7 +16,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - downloadSources := context.Config().DownloadSourcePackages || context.flags.Lookup("with-sources").Value.Get().(bool) + downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.flags, "with-sources") + downloadUdebs := context.flags.Lookup("with-udebs").Value.Get().(bool) var ( mirrorName, archiveURL, distribution string @@ -33,7 +34,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { archiveURL, distribution, components = args[1], args[2], args[3:] } - repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(), downloadSources) + repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(), + downloadSources, downloadUdebs) if err != nil { return fmt.Errorf("unable to create mirror: %s", err) } @@ -74,7 +76,7 @@ func makeCmdMirrorCreate() *commander.Command { Short: "create new mirror", Long: ` Creates mirror of remote repository, aptly supports both regular and flat Debian repositories exported -via HTTP. aptly would try download Release file from remote repository and verify its' signature. Command +via HTTP and FTP. aptly would try download Release file from remote repository and verify its' signature. Command line format resembles apt utlitily sources.list(5). PPA urls could specified in short format: @@ -90,6 +92,7 @@ Example: cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures") cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages") + cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)") cmd.Flag.String("filter", "", "filter packages in mirror") cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)") diff --git a/src/github.com/smira/aptly/cmd/mirror_drop.go b/src/github.com/smira/aptly/cmd/mirror_drop.go index 97c62e40..ba9e8281 100644 --- a/src/github.com/smira/aptly/cmd/mirror_drop.go +++ b/src/github.com/smira/aptly/cmd/mirror_drop.go @@ -20,6 +20,11 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to drop: %s", err) } + err = repo.CheckLock() + if err != nil { + return fmt.Errorf("unable to drop: %s", err) + } + force := context.flags.Lookup("force").Value.Get().(bool) if !force { snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo) diff --git a/src/github.com/smira/aptly/cmd/mirror_edit.go b/src/github.com/smira/aptly/cmd/mirror_edit.go index 15f41f64..0cea1034 100644 --- a/src/github.com/smira/aptly/cmd/mirror_edit.go +++ b/src/github.com/smira/aptly/cmd/mirror_edit.go @@ -19,15 +19,28 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to edit: %s", err) } + err = repo.CheckLock() + if err != nil { + return fmt.Errorf("unable to edit: %s", err) + } + context.flags.Visit(func(flag *flag.Flag) { switch flag.Name { case "filter": repo.Filter = flag.Value.String() case "filter-with-deps": repo.FilterWithDeps = flag.Value.Get().(bool) + case "with-sources": + repo.DownloadSources = flag.Value.Get().(bool) + case "with-udebs": + repo.DownloadUdebs = flag.Value.Get().(bool) } }) + if repo.IsFlat() && repo.DownloadUdebs { + return fmt.Errorf("unable to edit: flat mirrors don't support udebs") + } + if repo.Filter != "" { _, err = query.Parse(repo.Filter) if err != nil { @@ -35,6 +48,15 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { } } + if context.globalFlags.Lookup("architectures").Value.String() != "" { + repo.Architectures = context.ArchitecturesList() + + err = repo.Fetch(context.Downloader(), nil) + if err != nil { + return fmt.Errorf("unable to edit: %s", err) + } + } + err = context.CollectionFactory().RemoteRepoCollection().Update(repo) if err != nil { return fmt.Errorf("unable to edit: %s", err) @@ -48,10 +70,10 @@ func makeCmdMirrorEdit() *commander.Command { cmd := &commander.Command{ Run: aptlyMirrorEdit, UsageLine: "edit ", - Short: "edit properties of mirorr", + Short: "edit mirror settings", Long: ` -Command edit allows to change settings of mirror: -filters. +Command edit allows one to change settings of mirror: +filters, list of architectures. Example: @@ -62,6 +84,8 @@ Example: cmd.Flag.String("filter", "", "filter packages in mirror") cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") + cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages") + cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)") return cmd } diff --git a/src/github.com/smira/aptly/cmd/mirror_rename.go b/src/github.com/smira/aptly/cmd/mirror_rename.go index 5cbfebe5..68d2f8e3 100644 --- a/src/github.com/smira/aptly/cmd/mirror_rename.go +++ b/src/github.com/smira/aptly/cmd/mirror_rename.go @@ -24,6 +24,11 @@ func aptlyMirrorRename(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to rename: %s", err) } + err = repo.CheckLock() + if err != nil { + return fmt.Errorf("unable to rename: %s", err) + } + _, err = context.CollectionFactory().RemoteRepoCollection().ByName(newName) if err == nil { return fmt.Errorf("unable to rename: mirror %s already exists", newName) diff --git a/src/github.com/smira/aptly/cmd/mirror_search.go b/src/github.com/smira/aptly/cmd/mirror_search.go new file mode 100644 index 00000000..23205a33 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/mirror_search.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/smira/commander" + "github.com/smira/flag" +) + +func makeCmdMirrorSearch() *commander.Command { + cmd := &commander.Command{ + Run: aptlySnapshotMirrorRepoSearch, + UsageLine: "search ", + Short: "search mirror for packages matching query", + Long: ` +Command search displays list of packages in mirror that match package query + +Example: + + $ aptly mirror search wheezy-main '$Architecture (i386), Name (% *-dev)' +`, + Flag: *flag.NewFlagSet("aptly-mirror-show", flag.ExitOnError), + } + + cmd.Flag.Bool("with-deps", false, "include dependencies into search results") + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/mirror_show.go b/src/github.com/smira/aptly/cmd/mirror_show.go index 53296cdb..d2fea81f 100644 --- a/src/github.com/smira/aptly/cmd/mirror_show.go +++ b/src/github.com/smira/aptly/cmd/mirror_show.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/smira/aptly/deb" "github.com/smira/aptly/utils" "github.com/smira/commander" "github.com/smira/flag" @@ -28,6 +29,9 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error { } fmt.Printf("Name: %s\n", repo.Name) + if repo.Status == deb.MirrorUpdating { + fmt.Printf("Status: In Update (PID %d)\n", repo.WorkerPID) + } fmt.Printf("Archive Root URL: %s\n", repo.ArchiveRoot) fmt.Printf("Distribution: %s\n", repo.Distribution) fmt.Printf("Components: %s\n", strings.Join(repo.Components, ", ")) @@ -37,6 +41,11 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error { downloadSources = "yes" } fmt.Printf("Download Sources: %s\n", downloadSources) + downloadUdebs := "no" + if repo.DownloadUdebs { + downloadUdebs = "yes" + } + fmt.Printf("Download .udebs: %s\n", downloadUdebs) if repo.Filter != "" { fmt.Printf("Filter: %s\n", repo.Filter) filterWithDeps := "no" diff --git a/src/github.com/smira/aptly/cmd/mirror_update.go b/src/github.com/smira/aptly/cmd/mirror_update.go index 7caf564d..5b5cc285 100644 --- a/src/github.com/smira/aptly/cmd/mirror_update.go +++ b/src/github.com/smira/aptly/cmd/mirror_update.go @@ -4,8 +4,12 @@ import ( "fmt" "github.com/smira/aptly/deb" "github.com/smira/aptly/query" + "github.com/smira/aptly/utils" "github.com/smira/commander" "github.com/smira/flag" + "os" + "os/signal" + "strings" ) func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { @@ -27,6 +31,14 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } + force := context.flags.Lookup("force").Value.Get().(bool) + if !force { + err = repo.CheckLock() + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + } + ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool) verifier, err := getVerifier(context.flags) @@ -39,21 +51,112 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } - var filterQuery deb.PackageQuery - - if repo.Filter != "" { - filterQuery, err = query.Parse(repo.Filter) - if err != nil { - return fmt.Errorf("unable to update: %s", err) - } - } - - err = repo.Download(context.Progress(), context.Downloader(), context.CollectionFactory(), context.PackagePool(), ignoreMismatch, - context.DependencyOptions(), filterQuery) + context.Progress().Printf("Downloading & parsing package files...\n") + err = repo.DownloadPackageIndexes(context.Progress(), context.Downloader(), context.CollectionFactory(), ignoreMismatch) if err != nil { return fmt.Errorf("unable to update: %s", err) } + if repo.Filter != "" { + context.Progress().Printf("Applying filter...\n") + var filterQuery deb.PackageQuery + + filterQuery, err = query.Parse(repo.Filter) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + var oldLen, newLen int + oldLen, newLen, err = repo.ApplyFilter(context.DependencyOptions(), filterQuery) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + context.Progress().Printf("Packages filtered: %d -> %d.\n", oldLen, newLen) + } + + var ( + downloadSize int64 + queue []deb.PackageDownloadTask + ) + + context.Progress().Printf("Building download queue...\n") + queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool()) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + defer func() { + // on any interruption, unlock the mirror + err := context.ReOpenDatabase() + if err == nil { + repo.MarkAsIdle() + context.CollectionFactory().RemoteRepoCollection().Update(repo) + } + }() + + repo.MarkAsUpdating() + err = context.CollectionFactory().RemoteRepoCollection().Update(repo) + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + err = context.CloseDatabase() + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + // Catch ^C + sigch := make(chan os.Signal) + signal.Notify(sigch, os.Interrupt) + + count := len(queue) + context.Progress().Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize)) + + // Download from the queue + context.Progress().InitBar(downloadSize, true) + + // Download all package files + ch := make(chan error, count) + + // In separate goroutine (to avoid blocking main), push queue to downloader + go func() { + for _, task := range queue { + context.Downloader().DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch) + } + + // We don't need queue after this point + queue = nil + }() + + // Wait for all downloads to finish + errors := make([]string, 0) + + for count > 0 { + select { + case <-sigch: + signal.Stop(sigch) + return fmt.Errorf("unable to update: interrupted") + case err = <-ch: + if err != nil { + errors = append(errors, err.Error()) + } + count-- + } + } + + context.Progress().ShutdownBar() + signal.Stop(sigch) + + if len(errors) > 0 { + return fmt.Errorf("unable to update: download errors:\n %s\n", strings.Join(errors, "\n ")) + } + + err = context.ReOpenDatabase() + if err != nil { + return fmt.Errorf("unable to update: %s", err) + } + + repo.FinalizeDownload() err = context.CollectionFactory().RemoteRepoCollection().Update(repo) if err != nil { return fmt.Errorf("unable to update: %s", err) @@ -80,6 +183,7 @@ Example: Flag: *flag.NewFlagSet("aptly-mirror-update", flag.ExitOnError), } + cmd.Flag.Bool("force", false, "force update mirror even if it is locked by another process") cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata") cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures") cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)") diff --git a/src/github.com/smira/aptly/cmd/package.go b/src/github.com/smira/aptly/cmd/package.go new file mode 100644 index 00000000..3c9d428c --- /dev/null +++ b/src/github.com/smira/aptly/cmd/package.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "github.com/smira/commander" +) + +func makeCmdPackage() *commander.Command { + return &commander.Command{ + UsageLine: "package", + Short: "operations on packages", + Subcommands: []*commander.Command{ + makeCmdPackageSearch(), + makeCmdPackageShow(), + }, + } +} diff --git a/src/github.com/smira/aptly/cmd/package_search.go b/src/github.com/smira/aptly/cmd/package_search.go new file mode 100644 index 00000000..8c910548 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/package_search.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "fmt" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/query" + "github.com/smira/commander" + "github.com/smira/flag" +) + +func aptlyPackageSearch(cmd *commander.Command, args []string) error { + var err error + if len(args) != 1 { + cmd.Usage() + return commander.ErrCommandError + } + + q, err := query.Parse(args[0]) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + result := q.Query(context.CollectionFactory().PackageCollection()) + result.ForEach(func(p *deb.Package) error { + context.Progress().Printf("%s\n", p) + return nil + }) + + return err +} + +func makeCmdPackageSearch() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPackageSearch, + UsageLine: "search ", + Short: "search for packages matching query", + Long: ` +Command search displays list of packages in whole DB that match package query + +Example: + + $ aptly package search '$Architecture (i386), Name (% *-dev)' +`, + Flag: *flag.NewFlagSet("aptly-package-search", flag.ExitOnError), + } + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/package_show.go b/src/github.com/smira/aptly/cmd/package_show.go new file mode 100644 index 00000000..d544f0a8 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/package_show.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "bufio" + "fmt" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/query" + "github.com/smira/commander" + "github.com/smira/flag" + "os" +) + +func printReferencesTo(p *deb.Package) (err error) { + err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error { + err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) + if err != nil { + return err + } + if repo.RefList() != nil { + if repo.RefList().Has(p) { + fmt.Printf(" mirror %s\n", repo) + } + } + return nil + }) + if err != nil { + return err + } + + err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error { + err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo) + if err != nil { + return err + } + if repo.RefList() != nil { + if repo.RefList().Has(p) { + fmt.Printf(" local repo %s\n", repo) + } + } + return nil + }) + if err != nil { + return err + } + + err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error { + err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot) + if err != nil { + return err + } + if snapshot.RefList().Has(p) { + fmt.Printf(" snapshot %s\n", snapshot) + } + return nil + }) + if err != nil { + return err + } + + return nil +} + +func aptlyPackageShow(cmd *commander.Command, args []string) error { + var err error + if len(args) != 1 { + cmd.Usage() + return commander.ErrCommandError + } + + q, err := query.Parse(args[0]) + if err != nil { + return fmt.Errorf("unable to show: %s", err) + } + + withFiles := context.flags.Lookup("with-files").Value.Get().(bool) + withReferences := context.flags.Lookup("with-references").Value.Get().(bool) + + w := bufio.NewWriter(os.Stdout) + + result := q.Query(context.CollectionFactory().PackageCollection()) + + err = result.ForEach(func(p *deb.Package) error { + p.Stanza().WriteTo(w) + w.Flush() + fmt.Printf("\n") + + if withFiles { + fmt.Printf("Files in the pool:\n") + for _, f := range p.Files() { + path, err := context.PackagePool().Path(f.Filename, f.Checksums.MD5) + if err != nil { + return err + } + fmt.Printf(" %s\n", path) + } + fmt.Printf("\n") + } + + if withReferences { + fmt.Printf("References to package:\n") + printReferencesTo(p) + fmt.Printf("\n") + } + + return nil + }) + + if err != nil { + return fmt.Errorf("unable to show: %s", err) + } + + return err +} + +func makeCmdPackageShow() *commander.Command { + cmd := &commander.Command{ + Run: aptlyPackageShow, + UsageLine: "show ", + Short: "show details about packages matcing query", + Long: ` +Command shows displays detailed meta-information about packages +matching query. Information from Debian control file is displayed. +Optionally information about package files and +inclusion into mirrors/snapshots/local repos is shown. + +Example: + + $ aptly package show nginx-light_1.2.1-2.2+wheezy2_i386' +`, + Flag: *flag.NewFlagSet("aptly-package-show", flag.ExitOnError), + } + + cmd.Flag.Bool("with-files", false, "display information about files from package pool") + cmd.Flag.Bool("with-references", false, "display information about mirrors, snapshots and local repos referencing this package") + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/publish.go b/src/github.com/smira/aptly/cmd/publish.go index b6cb2066..1ad8b163 100644 --- a/src/github.com/smira/aptly/cmd/publish.go +++ b/src/github.com/smira/aptly/cmd/publish.go @@ -8,13 +8,14 @@ import ( ) func getSigner(flags *flag.FlagSet) (utils.Signer, error) { - if flags.Lookup("skip-signing").Value.Get().(bool) || context.Config().GpgDisableSign { + if LookupOption(context.Config().GpgDisableSign, flags, "skip-signing") { return nil, nil } signer := &utils.GpgSigner{} signer.SetKey(flags.Lookup("gpg-key").Value.String()) signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String()) + signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String()) err := signer.Init() if err != nil { diff --git a/src/github.com/smira/aptly/cmd/publish_list.go b/src/github.com/smira/aptly/cmd/publish_list.go index c352abda..20d89bcc 100644 --- a/src/github.com/smira/aptly/cmd/publish_list.go +++ b/src/github.com/smira/aptly/cmd/publish_list.go @@ -25,7 +25,7 @@ func aptlyPublishList(cmd *commander.Command, args []string) error { } if raw { - published = append(published, fmt.Sprintf("%s %s", repo.Prefix, repo.Distribution)) + published = append(published, fmt.Sprintf("%s %s", repo.StoragePrefix(), repo.Distribution)) } else { published = append(published, repo.String()) } diff --git a/src/github.com/smira/aptly/cmd/publish_repo.go b/src/github.com/smira/aptly/cmd/publish_repo.go index 2545d4e0..405c5970 100644 --- a/src/github.com/smira/aptly/cmd/publish_repo.go +++ b/src/github.com/smira/aptly/cmd/publish_repo.go @@ -37,6 +37,8 @@ Example: cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") + cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") + cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("label", "", "label to publish") diff --git a/src/github.com/smira/aptly/cmd/publish_snapshot.go b/src/github.com/smira/aptly/cmd/publish_snapshot.go index f02f96aa..92877522 100644 --- a/src/github.com/smira/aptly/cmd/publish_snapshot.go +++ b/src/github.com/smira/aptly/cmd/publish_snapshot.go @@ -199,6 +199,8 @@ Example: cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") + cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") + cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("label", "", "label to publish") diff --git a/src/github.com/smira/aptly/cmd/publish_switch.go b/src/github.com/smira/aptly/cmd/publish_switch.go index ed4dddcb..21b97eca 100644 --- a/src/github.com/smira/aptly/cmd/publish_switch.go +++ b/src/github.com/smira/aptly/cmd/publish_switch.go @@ -131,6 +131,8 @@ Example: cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") + cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") + cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") diff --git a/src/github.com/smira/aptly/cmd/publish_update.go b/src/github.com/smira/aptly/cmd/publish_update.go index 0833d136..0629acdc 100644 --- a/src/github.com/smira/aptly/cmd/publish_update.go +++ b/src/github.com/smira/aptly/cmd/publish_update.go @@ -98,6 +98,8 @@ Example: cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)") cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") + cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") + cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") diff --git a/src/github.com/smira/aptly/cmd/repo.go b/src/github.com/smira/aptly/cmd/repo.go index 94ae0b2e..2823a149 100644 --- a/src/github.com/smira/aptly/cmd/repo.go +++ b/src/github.com/smira/aptly/cmd/repo.go @@ -20,6 +20,7 @@ func makeCmdRepo() *commander.Command { makeCmdRepoRemove(), makeCmdRepoShow(), makeCmdRepoRename(), + makeCmdRepoSearch(), }, } } diff --git a/src/github.com/smira/aptly/cmd/repo_add.go b/src/github.com/smira/aptly/cmd/repo_add.go index e003bed4..7626efc0 100644 --- a/src/github.com/smira/aptly/cmd/repo_add.go +++ b/src/github.com/smira/aptly/cmd/repo_add.go @@ -40,6 +40,8 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to load packages: %s", err) } + forceReplace := context.flags.Lookup("force-replace").Value.Get().(bool) + packageFiles := []string{} failedFiles := []string{} @@ -59,14 +61,16 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { return nil } - if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") { + if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || + strings.HasSuffix(info.Name(), ".dsc") { packageFiles = append(packageFiles, path) } return nil }) } else { - if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".dsc") { + if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || + strings.HasSuffix(info.Name(), ".dsc") { packageFiles = append(packageFiles, location) } else { context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location) @@ -79,6 +83,10 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { processedFiles := []string{} sort.Strings(packageFiles) + if forceReplace { + list.PrepareIndex() + } + for _, file := range packageFiles { var ( stanza deb.Stanza @@ -87,6 +95,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { candidateProcessedFiles := []string{} isSourcePackage := strings.HasSuffix(file, ".dsc") + isUdebPackage := strings.HasSuffix(file, ".udeb") if isSourcePackage { stanza, err = deb.GetControlFileFromDsc(file, verifier) @@ -99,7 +108,11 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { } } else { stanza, err = deb.GetControlFileFromDeb(file) - p = deb.NewPackageFromControlFile(stanza) + if isUdebPackage { + p = deb.NewUdebPackageFromControlFile(stanza) + } else { + p = deb.NewPackageFromControlFile(stanza) + } } if err != nil { context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err) @@ -155,6 +168,14 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { continue } + if forceReplace { + conflictingPackages := list.Search(deb.Dependency{Pkg: p.Name, Version: p.Version, Architecture: p.Architecture}, true) + for _, cp := range conflictingPackages { + context.Progress().ColoredPrintf("@r[-]@| %s removed due to conflict with package being added", cp) + list.Remove(cp) + } + } + err = list.Add(p) if err != nil { context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err) @@ -202,8 +223,8 @@ func makeCmdRepoAdd() *commander.Command { UsageLine: "add | ...", Short: "add packages to local repository", Long: ` -Command adds packages to local repository from .deb (binary packages) and .dsc (source packages) files. -When importing from directory aptly would do recursive scan looking for all files matching *.deb or *.dsc +Command adds packages to local repository from .deb, .udeb (binary packages) and .dsc (source packages) files. +When importing from directory aptly would do recursive scan looking for all files matching *.[u]deb or *.dsc patterns. Every file discovered would be analyzed to extract metadata, package would then be created and added to the database. Files would be imported to internal package pool. For source packages, all required files are added automatically as well. Extra files for source package should be in the same directory as *.dsc file. @@ -216,6 +237,7 @@ Example: } cmd.Flag.Bool("remove-files", false, "remove files that have been imported successfully into repository") + cmd.Flag.Bool("force-replace", false, "when adding package that conflicts with existing package, remove existing package") return cmd } diff --git a/src/github.com/smira/aptly/cmd/repo_edit.go b/src/github.com/smira/aptly/cmd/repo_edit.go index dce6b2bc..6177be43 100644 --- a/src/github.com/smira/aptly/cmd/repo_edit.go +++ b/src/github.com/smira/aptly/cmd/repo_edit.go @@ -50,7 +50,7 @@ func makeCmdRepoEdit() *commander.Command { UsageLine: "edit ", Short: "edit properties of local repository", Long: ` -Command edit allows to change metadata of local repository: +Command edit allows one to change metadata of local repository: comment, default distribution and component. Example: diff --git a/src/github.com/smira/aptly/cmd/repo_search.go b/src/github.com/smira/aptly/cmd/repo_search.go new file mode 100644 index 00000000..e681a339 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/repo_search.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/smira/commander" + "github.com/smira/flag" +) + +func makeCmdRepoSearch() *commander.Command { + cmd := &commander.Command{ + Run: aptlySnapshotMirrorRepoSearch, + UsageLine: "search ", + Short: "search repo for packages matching query", + Long: ` +Command search displays list of packages in local repository that match package query + +Example: + + $ aptly repo search my-software '$Architecture (i386), Name (% *-dev)' +`, + Flag: *flag.NewFlagSet("aptly-repo-show", flag.ExitOnError), + } + + cmd.Flag.Bool("with-deps", false, "include dependencies into search results") + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/run.go b/src/github.com/smira/aptly/cmd/run.go new file mode 100644 index 00000000..6ac45138 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/run.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "fmt" + "github.com/smira/commander" +) + +// Run runs single command starting from root cmd with args, optionally initializing context +func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) { + defer func() { + if r := recover(); r != nil { + fatal, ok := r.(*FatalError) + if !ok { + panic(r) + } + fmt.Println("ERROR:", fatal.Message) + returnCode = fatal.ReturnCode + } + }() + + returnCode = 0 + + flags, args, err := cmd.ParseFlags(cmdArgs) + if err != nil { + Fatal(err) + } + + if initContext { + err = InitContext(flags) + if err != nil { + Fatal(err) + } + defer ShutdownContext() + } + + context.UpdateFlags(flags) + + err = cmd.Dispatch(args) + if err != nil { + Fatal(err) + } + + return +} diff --git a/src/github.com/smira/aptly/cmd/snapshot.go b/src/github.com/smira/aptly/cmd/snapshot.go index 47207921..d0cf2aea 100644 --- a/src/github.com/smira/aptly/cmd/snapshot.go +++ b/src/github.com/smira/aptly/cmd/snapshot.go @@ -18,6 +18,8 @@ func makeCmdSnapshot() *commander.Command { makeCmdSnapshotMerge(), makeCmdSnapshotDrop(), makeCmdSnapshotRename(), + makeCmdSnapshotSearch(), + makeCmdSnapshotFilter(), }, } } diff --git a/src/github.com/smira/aptly/cmd/snapshot_create.go b/src/github.com/smira/aptly/cmd/snapshot_create.go index c4e34be9..aa9078fc 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_create.go +++ b/src/github.com/smira/aptly/cmd/snapshot_create.go @@ -23,6 +23,11 @@ func aptlySnapshotCreate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to create snapshot: %s", err) } + err = repo.CheckLock() + if err != nil { + return fmt.Errorf("unable to create snapshot: %s", err) + } + err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) if err != nil { return fmt.Errorf("unable to create snapshot: %s", err) diff --git a/src/github.com/smira/aptly/cmd/snapshot_filter.go b/src/github.com/smira/aptly/cmd/snapshot_filter.go new file mode 100644 index 00000000..ce91fdc6 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/snapshot_filter.go @@ -0,0 +1,107 @@ +package cmd + +import ( + "fmt" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/query" + "github.com/smira/commander" + "github.com/smira/flag" + "sort" + "strings" +) + +func aptlySnapshotFilter(cmd *commander.Command, args []string) error { + var err error + if len(args) < 3 { + cmd.Usage() + return commander.ErrCommandError + } + + withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) + + // Load snapshot + source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) + if err != nil { + return fmt.Errorf("unable to filter: %s", err) + } + + err = context.CollectionFactory().SnapshotCollection().LoadComplete(source) + if err != nil { + return fmt.Errorf("unable to filter: %s", err) + } + + // Convert snapshot to package list + context.Progress().Printf("Loading packages (%d)...\n", source.RefList().Len()) + packageList, err := deb.NewPackageListFromRefList(source.RefList(), context.CollectionFactory().PackageCollection(), context.Progress()) + if err != nil { + return fmt.Errorf("unable to load packages: %s", err) + } + + context.Progress().Printf("Building indexes...\n") + packageList.PrepareIndex() + + // Calculate architectures + var architecturesList []string + + if len(context.ArchitecturesList()) > 0 { + architecturesList = context.ArchitecturesList() + } else { + architecturesList = packageList.Architectures(false) + } + + sort.Strings(architecturesList) + + if len(architecturesList) == 0 && withDeps { + return fmt.Errorf("unable to determine list of architectures, please specify explicitly") + } + + // Initial queries out of arguments + queries := make([]deb.PackageQuery, len(args)-2) + for i, arg := range args[2:] { + queries[i], err = query.Parse(arg) + if err != nil { + return fmt.Errorf("unable to parse query: %s", err) + } + } + + // Filter with dependencies as requested + result, err := packageList.Filter(queries, withDeps, nil, context.DependencyOptions(), architecturesList) + if err != nil { + return fmt.Errorf("unable to filter: %s", err) + } + + // Create snapshot + destination := deb.NewSnapshotFromPackageList(args[1], []*deb.Snapshot{source}, result, + fmt.Sprintf("Filtered '%s', query was: '%s'", source.Name, strings.Join(args[2:], " "))) + + err = context.CollectionFactory().SnapshotCollection().Add(destination) + if err != nil { + return fmt.Errorf("unable to create snapshot: %s", err) + } + + context.Progress().Printf("\nSnapshot %s successfully filtered.\nYou can run 'aptly publish snapshot %s' to publish snapshot as Debian repository.\n", destination.Name, destination.Name) + + return err +} + +func makeCmdSnapshotFilter() *commander.Command { + cmd := &commander.Command{ + Run: aptlySnapshotFilter, + UsageLine: "filter ...", + Short: "filter packages in snapshot producing another snapshot", + Long: ` +Command filter does filtering in snapshot , producing another +snapshot . Packages could be specified simply +as 'package-name' or as package queries. + +Example: + + $ aptly snapshot filter wheezy-main wheezy-required 'Priorioty (required)' +`, + Flag: *flag.NewFlagSet("aptly-snapshot-filter", flag.ExitOnError), + } + + cmd.Flag.Bool("with-deps", false, "include dependent packages as well") + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/snapshot_search.go b/src/github.com/smira/aptly/cmd/snapshot_search.go new file mode 100644 index 00000000..5768d65e --- /dev/null +++ b/src/github.com/smira/aptly/cmd/snapshot_search.go @@ -0,0 +1,125 @@ +package cmd + +import ( + "fmt" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/query" + "github.com/smira/commander" + "github.com/smira/flag" + "sort" +) + +func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error { + var err error + if len(args) != 2 { + cmd.Usage() + return commander.ErrCommandError + } + + name := args[0] + command := cmd.Parent.Name() + + var reflist *deb.PackageRefList + + if command == "snapshot" { + snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(name) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + err = context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + reflist = snapshot.RefList() + } else if command == "mirror" { + repo, err := context.CollectionFactory().RemoteRepoCollection().ByName(name) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + err = context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + reflist = repo.RefList() + } else if command == "repo" { + repo, err := context.CollectionFactory().LocalRepoCollection().ByName(name) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + err = context.CollectionFactory().LocalRepoCollection().LoadComplete(repo) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + reflist = repo.RefList() + } else { + panic("unknown command") + } + + list, err := deb.NewPackageListFromRefList(reflist, context.CollectionFactory().PackageCollection(), context.Progress()) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + list.PrepareIndex() + + q, err := query.Parse(args[1]) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) + architecturesList := []string{} + + if withDeps { + if len(context.ArchitecturesList()) > 0 { + architecturesList = context.ArchitecturesList() + } else { + architecturesList = list.Architectures(false) + } + + sort.Strings(architecturesList) + + if len(architecturesList) == 0 { + return fmt.Errorf("unable to determine list of architectures, please specify explicitly") + } + } + + result, err := list.Filter([]deb.PackageQuery{q}, withDeps, + nil, context.DependencyOptions(), architecturesList) + if err != nil { + return fmt.Errorf("unable to search: %s", err) + } + + result.ForEach(func(p *deb.Package) error { + context.Progress().Printf("%s\n", p) + return nil + }) + + return err +} + +func makeCmdSnapshotSearch() *commander.Command { + cmd := &commander.Command{ + Run: aptlySnapshotMirrorRepoSearch, + UsageLine: "search ", + Short: "search snapshot for packages matching query", + Long: ` +Command search displays list of packages in snapshot that match package query + +Example: + + $ aptly snapshot search wheezy-main '$Architecture (i386), Name (% *-dev)' +`, + Flag: *flag.NewFlagSet("aptly-snapshot-search", flag.ExitOnError), + } + + cmd.Flag.Bool("with-deps", false, "include dependencies into search results") + + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/task.go b/src/github.com/smira/aptly/cmd/task.go new file mode 100644 index 00000000..07d79de5 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/task.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/smira/commander" +) + +func makeCmdTask() *commander.Command { + return &commander.Command{ + UsageLine: "task", + Short: "manage aptly tasks", + Subcommands: []*commander.Command{ + makeCmdTaskRun(), + }, + } +} diff --git a/src/github.com/smira/aptly/cmd/task_run.go b/src/github.com/smira/aptly/cmd/task_run.go new file mode 100644 index 00000000..0a51c347 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/task_run.go @@ -0,0 +1,148 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/mattn/go-shellwords" + "github.com/smira/commander" +) + +func aptlyTaskRun(cmd *commander.Command, args []string) error { + var err error + var cmdList [][]string + + if filename := cmd.Flag.Lookup("filename").Value.Get().(string); filename != "" { + var text string + cmdArgs := []string{} + + if finfo, err := os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() { + return fmt.Errorf("no such file, %s\n", filename) + } + + fmt.Println("Reading file...\n") + + file, err := os.Open(filename) + + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + text = strings.TrimSpace(scanner.Text()) + "," + parsedArgs, _ := shellwords.Parse(text) + cmdArgs = append(cmdArgs, parsedArgs...) + } + + if err = scanner.Err(); err != nil { + return err + } + + if len(cmdArgs) == 0 { + return fmt.Errorf("the file is empty") + } + + cmdList = formatCommands(cmdArgs) + } else if len(args) == 0 { + var text string + cmdArgs := []string{} + + fmt.Println("Please enter one command per line and leave one blank when finished.") + + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("> ") + text, _ = reader.ReadString('\n') + if text == "\n" { + break + } else { + text = strings.TrimSpace(text) + "," + parsedArgs, _ := shellwords.Parse(text) + cmdArgs = append(cmdArgs, parsedArgs...) + } + } + + if len(cmdArgs) == 0 { + return fmt.Errorf("nothing entered") + } + + cmdList = formatCommands(cmdArgs) + } else { + cmdList = formatCommands(args) + } + + commandErrored := false + + for i, command := range cmdList { + if !commandErrored { + context.Progress().ColoredPrintf("@g%d) [Running]: %s@!", (i + 1), strings.Join(command, " ")) + context.Progress().ColoredPrintf("\n@yBegin command output: ----------------------------@!") + context.Progress().Flush() + + returnCode := Run(RootCommand(), command, false) + if returnCode != 0 { + commandErrored = true + } + context.Progress().ColoredPrintf("\n@yEnd command output: ------------------------------@!") + CleanupContext() + } else { + context.Progress().ColoredPrintf("@r%d) [Skipping]: %s@!", (i + 1), strings.Join(command, " ")) + } + } + + if commandErrored { + err = fmt.Errorf("at least one command has reported an error") + } + + return err +} + +func formatCommands(args []string) [][]string { + var cmd []string + var cmdArray [][]string + + for _, s := range args { + if sTrimmed := strings.TrimRight(s, ","); sTrimmed != s { + cmd = append(cmd, sTrimmed) + cmdArray = append(cmdArray, cmd) + cmd = []string{} + } else { + cmd = append(cmd, s) + } + } + + if len(cmd) > 0 { + cmdArray = append(cmdArray, cmd) + } + + return cmdArray +} + +func makeCmdTaskRun() *commander.Command { + cmd := &commander.Command{ + Run: aptlyTaskRun, + UsageLine: "run -filename= | , , ...", + Short: "run aptly tasks", + Long: ` +Command helps origanise multiple aptly commands in one single aptly task, running as single thread. + +Example: + + $ aptly task run + > repo create local + > repo add local pkg1 + > publish repo local + > serve + > + +`, + } + + cmd.Flag.String("filename", "", "specifies the filename that contains the commands to run") + return cmd +} diff --git a/src/github.com/smira/aptly/database/leveldb.go b/src/github.com/smira/aptly/database/leveldb.go index abb4fd5f..7876b38e 100644 --- a/src/github.com/smira/aptly/database/leveldb.go +++ b/src/github.com/smira/aptly/database/leveldb.go @@ -24,12 +24,14 @@ type Storage interface { KeysByPrefix(prefix []byte) [][]byte FetchByPrefix(prefix []byte) [][]byte Close() error + ReOpen() error StartBatch() FinishBatch() error CompactDB() error } type levelDB struct { + path string db *leveldb.DB batch *leveldb.Batch } @@ -39,17 +41,21 @@ var ( _ Storage = &levelDB{} ) -// OpenDB opens (creates) LevelDB database -func OpenDB(path string) (Storage, error) { +func internalOpen(path string) (*leveldb.DB, error) { o := &opt.Options{ Filter: filter.NewBloomFilter(10), } - db, err := leveldb.OpenFile(path, o) + return leveldb.OpenFile(path, o) +} + +// OpenDB opens (creates) LevelDB database +func OpenDB(path string) (Storage, error) { + db, err := internalOpen(path) if err != nil { return nil, err } - return &levelDB{db: db}, nil + return &levelDB{db: db, path: path}, nil } // RecoverDB recovers LevelDB database from corruption @@ -147,7 +153,23 @@ func (l *levelDB) FetchByPrefix(prefix []byte) [][]byte { // Close finishes DB work func (l *levelDB) Close() error { - return l.db.Close() + if l.db == nil { + return nil + } + err := l.db.Close() + l.db = nil + return err +} + +// Reopen tries to re-open the database +func (l *levelDB) ReOpen() error { + if l.db != nil { + return nil + } + + var err error + l.db, err = internalOpen(l.path) + return err } // StartBatch starts batch processing of keys diff --git a/src/github.com/smira/aptly/database/leveldb_test.go b/src/github.com/smira/aptly/database/leveldb_test.go index 90be5901..e2624723 100644 --- a/src/github.com/smira/aptly/database/leveldb_test.go +++ b/src/github.com/smira/aptly/database/leveldb_test.go @@ -155,3 +155,23 @@ func (s *LevelDBSuite) TestCompactDB(c *C) { c.Check(s.db.CompactDB(), IsNil) } + +func (s *LevelDBSuite) TestReOpen(c *C) { + var ( + key = []byte("key") + value = []byte("value") + ) + + err := s.db.Put(key, value) + c.Assert(err, IsNil) + + err = s.db.Close() + c.Assert(err, IsNil) + + err = s.db.ReOpen() + c.Assert(err, IsNil) + + result, err := s.db.Get(key) + c.Assert(err, IsNil) + c.Assert(result, DeepEquals, value) +} diff --git a/src/github.com/smira/aptly/deb/index_files.go b/src/github.com/smira/aptly/deb/index_files.go new file mode 100644 index 00000000..a1eb0015 --- /dev/null +++ b/src/github.com/smira/aptly/deb/index_files.go @@ -0,0 +1,254 @@ +package deb + +import ( + "bufio" + "fmt" + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/utils" + "os" + "path/filepath" + "strings" +) + +type indexFiles struct { + publishedStorage aptly.PublishedStorage + basePath string + renameMap map[string]string + generatedFiles map[string]utils.ChecksumInfo + tempDir string + suffix string + indexes map[string]*indexFile +} + +type indexFile struct { + parent *indexFiles + discardable bool + compressable bool + signable bool + relativePath string + tempFilename string + tempFile *os.File + w *bufio.Writer +} + +func (file *indexFile) BufWriter() (*bufio.Writer, error) { + if file.w == nil { + var err error + file.tempFilename = filepath.Join(file.parent.tempDir, strings.Replace(file.relativePath, "/", "_", -1)) + file.tempFile, err = os.Create(file.tempFilename) + if err != nil { + return nil, fmt.Errorf("unable to create temporary index file: %s", err) + } + + file.w = bufio.NewWriter(file.tempFile) + } + + return file.w, nil +} + +func (file *indexFile) Finalize(signer utils.Signer) error { + if file.w == nil { + if file.discardable { + return nil + } + file.BufWriter() + } + + err := file.w.Flush() + if err != nil { + file.tempFile.Close() + return fmt.Errorf("unable to write to index file: %s", err) + } + + if file.compressable { + err = utils.CompressFile(file.tempFile) + if err != nil { + file.tempFile.Close() + return fmt.Errorf("unable to compress index file: %s", err) + } + } + + file.tempFile.Close() + + exts := []string{""} + if file.compressable { + exts = append(exts, ".gz", ".bz2") + } + + for _, ext := range exts { + var checksumInfo utils.ChecksumInfo + + checksumInfo, err = utils.ChecksumsForFile(file.tempFilename + ext) + if err != nil { + return fmt.Errorf("unable to collect checksums: %s", err) + } + file.parent.generatedFiles[file.relativePath+ext] = checksumInfo + } + + err = file.parent.publishedStorage.MkDir(filepath.Dir(filepath.Join(file.parent.basePath, file.relativePath))) + if err != nil { + return fmt.Errorf("unable to create dir: %s", err) + } + + for _, ext := range exts { + err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext), + file.tempFilename+ext) + if err != nil { + return fmt.Errorf("unable to publish file: %s", err) + } + + if file.parent.suffix != "" { + file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+ext)] = + filepath.Join(file.parent.basePath, file.relativePath+ext) + } + } + + if file.signable && signer != nil { + err = signer.DetachedSign(file.tempFilename, file.tempFilename+".gpg") + if err != nil { + return fmt.Errorf("unable to detached sign file: %s", err) + } + + err = signer.ClearSign(file.tempFilename, filepath.Join(filepath.Dir(file.tempFilename), "In"+filepath.Base(file.tempFilename))) + if err != nil { + return fmt.Errorf("unable to clearsign file: %s", err) + } + + if file.parent.suffix != "" { + file.parent.renameMap[filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg")] = + filepath.Join(file.parent.basePath, file.relativePath+".gpg") + file.parent.renameMap[filepath.Join(file.parent.basePath, "In"+file.relativePath+file.parent.suffix)] = + filepath.Join(file.parent.basePath, "In"+file.relativePath) + } + + err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, file.relativePath+file.parent.suffix+".gpg"), + file.tempFilename+".gpg") + if err != nil { + return fmt.Errorf("unable to publish file: %s", err) + } + + err = file.parent.publishedStorage.PutFile(filepath.Join(file.parent.basePath, "In"+file.relativePath+file.parent.suffix), + filepath.Join(filepath.Dir(file.tempFilename), "In"+filepath.Base(file.tempFilename))) + if err != nil { + return fmt.Errorf("unable to publish file: %s", err) + } + } + + return nil +} + +func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string) *indexFiles { + return &indexFiles{ + publishedStorage: publishedStorage, + basePath: basePath, + renameMap: make(map[string]string), + generatedFiles: make(map[string]utils.ChecksumInfo), + tempDir: tempDir, + suffix: suffix, + indexes: make(map[string]*indexFile), + } +} + +func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexFile { + key := fmt.Sprintf("pi-%s-%s-%s", component, arch, udeb) + file, ok := files.indexes[key] + if !ok { + var relativePath string + + if arch == "source" { + relativePath = filepath.Join(component, "source", "Sources") + } else { + if udeb { + relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Packages") + } else { + relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages") + } + } + + file = &indexFile{ + parent: files, + discardable: false, + compressable: true, + signable: false, + relativePath: relativePath, + } + + files.indexes[key] = file + } + + return file +} + +func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexFile { + key := fmt.Sprintf("ri-%s-%s-%s", component, arch, udeb) + file, ok := files.indexes[key] + if !ok { + var relativePath string + + if arch == "source" { + relativePath = filepath.Join(component, "source", "Release") + } else { + if udeb { + relativePath = filepath.Join(component, "debian-installer", fmt.Sprintf("binary-%s", arch), "Release") + } else { + relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Release") + } + } + + file = &indexFile{ + parent: files, + discardable: udeb, + compressable: false, + signable: false, + relativePath: relativePath, + } + + files.indexes[key] = file + } + + return file +} + +func (files *indexFiles) ReleaseFile() *indexFile { + return &indexFile{ + parent: files, + discardable: false, + compressable: false, + signable: true, + relativePath: "Release", + } +} + +func (files *indexFiles) FinalizeAll(progress aptly.Progress) (err error) { + if progress != nil { + progress.InitBar(int64(len(files.indexes)), false) + defer progress.ShutdownBar() + } + + for _, file := range files.indexes { + err = file.Finalize(nil) + if err != nil { + return + } + if progress != nil { + progress.AddBar(1) + } + } + + files.indexes = make(map[string]*indexFile) + + return +} + +func (files *indexFiles) RenameFiles() error { + var err error + + for oldName, newName := range files.renameMap { + err = files.publishedStorage.RenameFile(oldName, newName) + if err != nil { + return fmt.Errorf("unable to rename: %s", err) + } + } + + return nil +} diff --git a/src/github.com/smira/aptly/deb/list.go b/src/github.com/smira/aptly/deb/list.go index 8825b768..3e78234b 100644 --- a/src/github.com/smira/aptly/deb/list.go +++ b/src/github.com/smira/aptly/deb/list.go @@ -41,6 +41,7 @@ type PackageList struct { // Verify interface var ( _ sort.Interface = &PackageList{} + _ PackageCatalog = &PackageList{} ) // NewPackageList creates empty package list @@ -235,6 +236,7 @@ func depSliceDeduplicate(s []Dependency) []Dependency { // // Analysis would be peformed for each architecture, in specified sources func (l *PackageList) VerifyDependencies(options int, architectures []string, sources *PackageList, progress aptly.Progress) ([]Dependency, error) { + l.PrepareIndex() missing := make([]Dependency, 0, 128) if progress != nil { @@ -244,7 +246,7 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so for _, arch := range architectures { cache := make(map[string]bool, 2048) - for _, p := range l.packages { + for _, p := range l.packagesIndex { if progress != nil { progress.AddBar(1) } @@ -262,7 +264,6 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so variants = depSliceDeduplicate(variants) variantsMissing := make([]Dependency, 0, len(variants)) - missingCount := 0 for _, dep := range variants { if dep.Architecture == "" { @@ -270,35 +271,23 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so } hash := dep.Hash() - r, ok := cache[hash] - if ok { - if !r { - missingCount++ - } - continue + satisfied, ok := cache[hash] + if !ok { + satisfied = sources.Search(dep, false) != nil + cache[hash] = satisfied } - if sources.Search(dep, false) == nil { + if !satisfied && !ok { variantsMissing = append(variantsMissing, dep) - missingCount++ - } else { - cache[hash] = true + } + + if satisfied && options&DepFollowAllVariants == 0 { + variantsMissing = nil + break } } - if options&DepFollowAllVariants == DepFollowAllVariants { - missing = append(missing, variantsMissing...) - for _, dep := range variantsMissing { - cache[dep.Hash()] = false - } - } else { - if missingCount == len(variants) { - missing = append(missing, variantsMissing...) - for _, dep := range variantsMissing { - cache[dep.Hash()] = false - } - } - } + missing = append(missing, variantsMissing...) } } } @@ -334,6 +323,10 @@ func (l *PackageList) Less(i, j int) bool { // PrepareIndex prepares list for indexing func (l *PackageList) PrepareIndex() { + if l.indexed { + return + } + l.packagesIndex = make([]*Package, l.Len()) l.providesIndex = make(map[string][]*Package, 128) @@ -364,6 +357,23 @@ func (l *PackageList) Scan(q PackageQuery) (result *PackageList) { return } +// SearchSupported returns true for PackageList +func (l *PackageList) SearchSupported() bool { + return true +} + +// SearchByKey looks up package by exact key reference +func (l *PackageList) SearchByKey(arch, name, version string) (result *PackageList) { + result = NewPackageList() + + pkg := l.packages["P"+arch+" "+name+" "+version] + if pkg != nil { + result.Add(pkg) + } + + return +} + // Search searches package index for specified package(s) using optimized queries func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []*Package) { if !l.indexed { @@ -414,6 +424,7 @@ func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, sour if withDependencies { added := result.Len() + result.PrepareIndex() dependencySource := NewPackageList() if source != nil { @@ -434,12 +445,21 @@ func (l *PackageList) Filter(queries []PackageQuery, withDependencies bool, sour // try to satisfy dependencies for _, dep := range missing { + // dependency might have already been satisfied + // with packages already been added + if result.Search(dep, false) != nil { + continue + } + searchResults := l.Search(dep, false) if searchResults != nil { for _, p := range searchResults { result.Add(p) dependencySource.Add(p) added++ + if dependencyOptions&DepFollowAllVariants == 0 { + break + } } } } diff --git a/src/github.com/smira/aptly/deb/package.go b/src/github.com/smira/aptly/deb/package.go index 976b8a2e..be734ed5 100644 --- a/src/github.com/smira/aptly/deb/package.go +++ b/src/github.com/smira/aptly/deb/package.go @@ -24,6 +24,8 @@ type Package struct { Provides []string // Is this source package IsSource bool + // Is this udeb package + IsUdeb bool // Hash of files section FilesHash uint64 // Is this >= 0.6 package? @@ -169,6 +171,14 @@ func NewSourcePackageFromControlFile(input Stanza) (*Package, error) { return result, nil } +// NewUdebPackageFromControlFile creates .udeb Package from parsed Debian control file +func NewUdebPackageFromControlFile(input Stanza) *Package { + p := NewPackageFromControlFile(input) + p.IsUdeb = true + + return p +} + // Key returns unique key identifying package func (p *Package) Key(prefix string) []byte { if p.V06Plus { @@ -220,6 +230,9 @@ func (p *Package) GetField(name string) string { if p.IsSource { return "source" } + if p.IsUdeb { + return "udeb" + } return "deb" case "Name": return p.Name diff --git a/src/github.com/smira/aptly/deb/package_collection.go b/src/github.com/smira/aptly/deb/package_collection.go index 54887279..10050788 100644 --- a/src/github.com/smira/aptly/deb/package_collection.go +++ b/src/github.com/smira/aptly/deb/package_collection.go @@ -15,6 +15,11 @@ type PackageCollection struct { codecHandle *codec.MsgpackHandle } +// Verify interface +var ( + _ PackageCatalog = &PackageCollection{} +) + // NewPackageCollection creates new PackageCollection and binds it to database func NewPackageCollection(db database.Storage) *PackageCollection { return &PackageCollection{ @@ -237,3 +242,49 @@ func (collection *PackageCollection) DeleteByKey(key []byte) error { } return nil } + +// Scan does full scan on all the packages +func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) { + result = NewPackageList() + + for _, key := range collection.db.KeysByPrefix([]byte("P")) { + pkg, err := collection.ByKey(key) + if err != nil { + panic(fmt.Sprintf("unable to load package: %s", err)) + } + + if q.Matches(pkg) { + result.Add(pkg) + } + } + + return +} + +// Search is not implemented +func (collection *PackageCollection) Search(dep Dependency, allMatches bool) (searchResults []*Package) { + panic("Not implemented") +} + +// SearchSupported returns false +func (collection *PackageCollection) SearchSupported() bool { + return false +} + +// SearchByKey finds package by exact key +func (collection *PackageCollection) SearchByKey(arch, name, version string) (result *PackageList) { + result = NewPackageList() + + for _, key := range collection.db.KeysByPrefix([]byte(fmt.Sprintf("P%s %s %s", arch, name, version))) { + pkg, err := collection.ByKey(key) + if err != nil { + panic(fmt.Sprintf("unable to load package: %s", err)) + } + + if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version { + result.Add(pkg) + } + } + + return +} diff --git a/src/github.com/smira/aptly/deb/package_test.go b/src/github.com/smira/aptly/deb/package_test.go index efab20db..ddcf754e 100644 --- a/src/github.com/smira/aptly/deb/package_test.go +++ b/src/github.com/smira/aptly/deb/package_test.go @@ -28,6 +28,7 @@ func (s *PackageSuite) TestNewFromPara(c *C) { p := NewPackageFromControlFile(s.stanza) c.Check(p.IsSource, Equals, false) + c.Check(p.IsUdeb, Equals, false) c.Check(p.Name, Equals, "alien-arena-common") c.Check(p.Version, Equals, "7.40-2") c.Check(p.Architecture, Equals, "i386") @@ -40,11 +41,27 @@ func (s *PackageSuite) TestNewFromPara(c *C) { c.Check(p.deps.Depends, DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)"}) } +func (s *PackageSuite) TestNewUdebFromPara(c *C) { + stanza, _ := NewControlFileReader(bytes.NewBufferString(udebPackageMeta)).ReadStanza() + p := NewUdebPackageFromControlFile(stanza) + + c.Check(p.IsSource, Equals, false) + c.Check(p.IsUdeb, Equals, true) + c.Check(p.Name, Equals, "dmidecode-udeb") + c.Check(p.Version, Equals, "2.11-9") + c.Check(p.Architecture, Equals, "amd64") + c.Check(p.Provides, DeepEquals, []string(nil)) + c.Check(p.Files(), HasLen, 1) + c.Check(p.Files()[0].Filename, Equals, "dmidecode-udeb_2.11-9_amd64.udeb") + c.Check(p.deps.Depends, DeepEquals, []string{"libc6-udeb (>= 2.13)"}) +} + func (s *PackageSuite) TestNewSourceFromPara(c *C) { p, err := NewSourcePackageFromControlFile(s.sourceStanza) c.Check(err, IsNil) c.Check(p.IsSource, Equals, true) + c.Check(p.IsUdeb, Equals, false) c.Check(p.Name, Equals, "access-modifier-checker") c.Check(p.Version, Equals, "1.0-4") c.Check(p.Architecture, Equals, "source") @@ -134,21 +151,28 @@ func (s *PackageSuite) TestGetField(c *C) { p4, _ := NewSourcePackageFromControlFile(s.sourceStanza.Copy()) + stanza5, _ := NewControlFileReader(bytes.NewBufferString(udebPackageMeta)).ReadStanza() + p5 := NewUdebPackageFromControlFile(stanza5) + c.Check(p.GetField("$Source"), Equals, "alien-arena") c.Check(p2.GetField("$Source"), Equals, "alien-arena-common") c.Check(p3.GetField("$Source"), Equals, "alien-arena") c.Check(p4.GetField("$Source"), Equals, "") + c.Check(p5.GetField("$Source"), Equals, "dmidecode") c.Check(p.GetField("$SourceVersion"), Equals, "7.40-2") c.Check(p2.GetField("$SourceVersion"), Equals, "7.40-2") c.Check(p3.GetField("$SourceVersion"), Equals, "3.5") c.Check(p4.GetField("$SourceVersion"), Equals, "") + c.Check(p5.GetField("$SourceVersion"), Equals, "2.11-9") c.Check(p.GetField("$Architecture"), Equals, "i386") c.Check(p4.GetField("$Architecture"), Equals, "source") + c.Check(p5.GetField("$Architecture"), Equals, "amd64") c.Check(p.GetField("$PackageType"), Equals, "deb") c.Check(p4.GetField("$PackageType"), Equals, "source") + c.Check(p5.GetField("$PackageType"), Equals, "udeb") c.Check(p.GetField("Name"), Equals, "alien-arena-common") c.Check(p4.GetField("Name"), Equals, "access-modifier-checker") @@ -455,3 +479,20 @@ Directory: pool/main/a/access-modifier-checker Priority: source Section: java ` + +const udebPackageMeta = `Package: dmidecode-udeb +Source: dmidecode +Version: 2.11-9 +Installed-Size: 115 +Maintainer: Daniel Baumann +Architecture: amd64 +Depends: libc6-udeb (>= 2.13) +Description: SMBIOS/DMI table decoder (udeb) +Description-md5: bdfb786c6a57097be8c8600b800e749f +Section: debian-installer +Priority: optional +Filename: pool/main/d/dmidecode/dmidecode-udeb_2.11-9_amd64.udeb +Size: 29188 +MD5sum: ae70341c4d96dcded89fa670bcfea31e +SHA1: 9532ae4226a85805189a671ee0283f719d48a5ba +SHA256: bbb3a2cb07f741c3995b6d4bb08d772d83582b93a0236d4ea7736bc0370fc320` diff --git a/src/github.com/smira/aptly/deb/publish.go b/src/github.com/smira/aptly/deb/publish.go index efb9969a..7e1c5f9f 100644 --- a/src/github.com/smira/aptly/deb/publish.go +++ b/src/github.com/smira/aptly/deb/publish.go @@ -1,7 +1,6 @@ package deb import ( - "bufio" "bytes" "code.google.com/p/go-uuid/uuid" "fmt" @@ -169,6 +168,9 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri if distribution == "" || component == "" { rootDistributions, rootComponents := walkUpTree(source, collectionFactory) if distribution == "" { + for i := range rootDistributions { + rootDistributions[i] = strings.Replace(rootDistributions[i], "/", "-", -1) + } discoveredDistributions = append(discoveredDistributions, rootDistributions...) } if component == "" { @@ -228,6 +230,10 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri } } + if strings.Index(distribution, "/") != -1 { + return nil, fmt.Errorf("invalid distribution %s, '/' is not allowed", distribution) + } + result.Distribution = distribution return result, nil @@ -440,9 +446,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP suffix = ".tmp" } - generatedFiles := map[string]utils.ChecksumInfo{} - renameMap := map[string]string{} - if progress != nil { progress.Printf("Generating metadata files and linking package files...\n") } @@ -454,41 +457,44 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP } defer os.RemoveAll(tempDir) + indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix) + for component, list := range lists { - var relativePath string + hadUdebs := false - // For all architectures, generate packages/sources files + // For all architectures, pregenerate packages/sources files for _, arch := range p.Architectures { + indexes.PackageIndex(component, arch, false) + } + + if progress != nil { + progress.InitBar(int64(list.Len()), false) + } + + err = list.ForEach(func(pkg *Package) error { if progress != nil { - progress.InitBar(int64(list.Len()), false) + progress.AddBar(1) } - if arch == "source" { - relativePath = filepath.Join(component, "source", "Sources") - } else { - relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Packages") - } - err = publishedStorage.MkDir(filepath.Dir(filepath.Join(basePath, relativePath))) - if err != nil { - return err - } - - var packagesFile *os.File - - packagesFileName := filepath.Join(tempDir, fmt.Sprintf("pkgs_%s_%s", component, arch)) - packagesFile, err = os.Create(packagesFileName) - if err != nil { - return fmt.Errorf("unable to create temporary Packages file: %s", err) - } - - bufWriter := bufio.NewWriter(packagesFile) - - err = list.ForEach(func(pkg *Package) error { - if progress != nil { - progress.AddBar(1) - } + matches := false + for _, arch := range p.Architectures { if pkg.MatchesArchitecture(arch) { - err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, component, forceOverwrite) + matches = true + break + } + } + + if matches { + hadUdebs = hadUdebs || pkg.IsUdeb + err = pkg.LinkFromPool(publishedStorage, packagePool, p.Prefix, component, forceOverwrite) + if err != nil { + return err + } + } + + for _, arch := range p.Architectures { + if pkg.MatchesArchitecture(arch) { + bufWriter, err := indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter() if err != nil { return err } @@ -501,113 +507,63 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP if err != nil { return err } - - pkg.files = nil - pkg.deps = nil - pkg.extra = nil - - } - - return nil - }) - - if err != nil { - return fmt.Errorf("unable to process packages: %s", err) - } - - err = bufWriter.Flush() - if err != nil { - return fmt.Errorf("unable to write Packages file: %s", err) - } - - err = utils.CompressFile(packagesFile) - if err != nil { - return fmt.Errorf("unable to compress Packages files: %s", err) - } - - packagesFile.Close() - - for _, ext := range []string{"", ".gz", ".bz2"} { - var checksumInfo utils.ChecksumInfo - - checksumInfo, err = utils.ChecksumsForFile(packagesFileName + ext) - if err != nil { - return fmt.Errorf("unable to collect checksums: %s", err) - } - generatedFiles[relativePath+ext] = checksumInfo - - err = publishedStorage.PutFile(filepath.Join(basePath, relativePath+suffix+ext), packagesFileName+ext) - if err != nil { - return fmt.Errorf("unable to publish file: %s", err) - } - - if suffix != "" { - renameMap[filepath.Join(basePath, relativePath+suffix+ext)] = filepath.Join(basePath, relativePath+ext) } } - if progress != nil { - progress.ShutdownBar() + pkg.files = nil + pkg.deps = nil + pkg.extra = nil + + return nil + }) + + if err != nil { + return fmt.Errorf("unable to process packages: %s", err) + } + + if progress != nil { + progress.ShutdownBar() + } + + udebs := []bool{false} + if hadUdebs { + udebs = append(udebs, true) + + // For all architectures, pregenerate .udeb indexes + for _, arch := range p.Architectures { + indexes.PackageIndex(component, arch, true) } } // For all architectures, generate Release files for _, arch := range p.Architectures { - release := make(Stanza) - release["Archive"] = p.Distribution - release["Architecture"] = arch - release["Component"] = component - release["Origin"] = p.GetOrigin() - release["Label"] = p.GetLabel() + for _, udeb := range udebs { + release := make(Stanza) + release["Archive"] = p.Distribution + release["Architecture"] = arch + release["Component"] = component + release["Origin"] = p.GetOrigin() + release["Label"] = p.GetLabel() - if arch == "source" { - relativePath = filepath.Join(component, "source", "Release") - } else { - relativePath = filepath.Join(component, fmt.Sprintf("binary-%s", arch), "Release") + bufWriter, err := indexes.ReleaseIndex(component, arch, udeb).BufWriter() + + err = release.WriteTo(bufWriter) + if err != nil { + return fmt.Errorf("unable to create Release file: %s", err) + } } - - var file *os.File - - fileName := filepath.Join(tempDir, fmt.Sprintf("release_%s_%s", component, arch)) - file, err = os.Create(fileName) - if err != nil { - return fmt.Errorf("unable to create temporary Release file: %s", err) - } - - bufWriter := bufio.NewWriter(file) - - err = release.WriteTo(bufWriter) - if err != nil { - return fmt.Errorf("unable to create Release file: %s", err) - } - - err = bufWriter.Flush() - if err != nil { - return fmt.Errorf("unable to create Release file: %s", err) - } - - file.Close() - - var checksumInfo utils.ChecksumInfo - checksumInfo, err = utils.ChecksumsForFile(fileName) - if err != nil { - return fmt.Errorf("unable to collect checksums: %s", err) - } - generatedFiles[relativePath] = checksumInfo - - err = publishedStorage.PutFile(filepath.Join(basePath, relativePath+suffix), fileName) - if err != nil { - file.Close() - return fmt.Errorf("unable to publish file: %s", err) - } - - if suffix != "" { - renameMap[filepath.Join(basePath, relativePath+suffix)] = filepath.Join(basePath, relativePath) - } - } } + if progress != nil { + progress.Printf("Finalizing metadata files...\n") + } + + err = indexes.FinalizeAll(progress) + if err != nil { + return err + } + release := make(Stanza) release["Origin"] = p.GetOrigin() release["Label"] = p.GetLabel() @@ -621,80 +577,36 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP release["Components"] = strings.Join(p.Components(), " ") - for path, info := range generatedFiles { + for path, info := range indexes.generatedFiles { release["MD5Sum"] += fmt.Sprintf(" %s %8d %s\n", info.MD5, info.Size, path) release["SHA1"] += fmt.Sprintf(" %s %8d %s\n", info.SHA1, info.Size, path) release["SHA256"] += fmt.Sprintf(" %s %8d %s\n", info.SHA256, info.Size, path) } - var releaseFile *os.File - releaseFilename := filepath.Join(tempDir, "Release") - releaseFile, err = os.Create(releaseFilename) + releaseFile := indexes.ReleaseFile() + bufWriter, err := releaseFile.BufWriter() if err != nil { - return fmt.Errorf("unable to create temporary Release file: %s", err) + return err } - bufWriter := bufio.NewWriter(releaseFile) - err = release.WriteTo(bufWriter) if err != nil { return fmt.Errorf("unable to create Release file: %s", err) } - err = bufWriter.Flush() - if err != nil { - return fmt.Errorf("unable to create Release file: %s", err) - } - - releaseFile.Close() - - if suffix != "" { - renameMap[filepath.Join(basePath, "Release"+suffix)] = filepath.Join(basePath, "Release") - } - - err = publishedStorage.PutFile(filepath.Join(basePath, "Release"+suffix), releaseFilename) - if err != nil { - return fmt.Errorf("unable to publish file: %s", err) - } - // Signing files might output to console, so flush progress writer first if progress != nil { progress.Flush() } - if signer != nil { - err = signer.DetachedSign(releaseFilename, releaseFilename+".gpg") - if err != nil { - return fmt.Errorf("unable to sign Release file: %s", err) - } - - err = signer.ClearSign(releaseFilename, filepath.Join(filepath.Dir(releaseFilename), "InRelease"+suffix)) - if err != nil { - return fmt.Errorf("unable to sign Release file: %s", err) - } - - if suffix != "" { - renameMap[filepath.Join(basePath, "Release"+suffix+".gpg")] = filepath.Join(basePath, "Release.gpg") - renameMap[filepath.Join(basePath, "InRelease"+suffix)] = filepath.Join(basePath, "InRelease") - } - - err = publishedStorage.PutFile(filepath.Join(basePath, "Release"+suffix+".gpg"), releaseFilename+".gpg") - if err != nil { - return fmt.Errorf("unable to publish file: %s", err) - } - - err = publishedStorage.PutFile(filepath.Join(basePath, "InRelease"+suffix), - filepath.Join(filepath.Dir(releaseFilename), "InRelease"+suffix)) - if err != nil { - return fmt.Errorf("unable to publish file: %s", err) - } + err = releaseFile.Finalize(signer) + if err != nil { + return err } - for oldName, newName := range renameMap { - err = publishedStorage.RenameFile(oldName, newName) - if err != nil { - return fmt.Errorf("unable to rename: %s", err) - } + err = indexes.RenameFiles() + if err != nil { + return err } return nil diff --git a/src/github.com/smira/aptly/deb/publish_test.go b/src/github.com/smira/aptly/deb/publish_test.go index f8a66672..60e3a226 100644 --- a/src/github.com/smira/aptly/deb/publish_test.go +++ b/src/github.com/smira/aptly/deb/publish_test.go @@ -39,6 +39,9 @@ func (n *NullSigner) SetKey(keyRef string) { func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) { } +func (n *NullSigner) SetPassphrase(passphrase, passphraseFile string) { +} + func (n *NullSigner) DetachedSign(source string, destination string) error { return ioutil.WriteFile(destination, []byte{}, 0644) } @@ -90,7 +93,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) { "files:other": s.publishedStorage2}} s.packagePool = files.NewPackagePool(s.root) - repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) repo.packageRefs = s.reflist s.factory.RemoteRepoCollection().Add(repo) @@ -164,6 +167,9 @@ func (s *PublishedRepoSuite) TestNewPublishedRepo(c *C) { _, err := NewPublishedRepo("", ".", "a", nil, []string{"main", "main"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) c.Check(err, ErrorMatches, "duplicate component name: main") + + _, err = NewPublishedRepo("", ".", "wheezy/updates", nil, []string{"main"}, []interface{}{s.snapshot}, s.factory) + c.Check(err, ErrorMatches, "invalid distribution wheezy/updates, '/' is not allowed") } func (s *PublishedRepoSuite) TestPrefixNormalization(c *C) { @@ -264,6 +270,13 @@ func (s *PublishedRepoSuite) TestDistributionComponentGuessing(c *C) { c.Check(repo.Distribution, Equals, "precise") c.Check(repo.Components(), DeepEquals, []string{"contrib"}) + s.localRepo.DefaultDistribution = "precise/updates" + + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{""}, []interface{}{s.localRepo}, s.factory) + c.Check(err, IsNil) + c.Check(repo.Distribution, Equals, "precise-updates") + c.Check(repo.Components(), DeepEquals, []string{"contrib"}) + repo, err = NewPublishedRepo("", "ppa", "", nil, []string{"", "contrib"}, []interface{}{s.snapshot, s.snapshot2}, s.factory) c.Check(err, IsNil) c.Check(repo.Distribution, Equals, "squeeze") diff --git a/src/github.com/smira/aptly/deb/query.go b/src/github.com/smira/aptly/deb/query.go index 6f279fe5..4dac528c 100644 --- a/src/github.com/smira/aptly/deb/query.go +++ b/src/github.com/smira/aptly/deb/query.go @@ -7,14 +7,22 @@ import ( "strings" ) +// PackageCatalog is abstraction on top of PackageCollection and PackageList +type PackageCatalog interface { + Scan(q PackageQuery) (result *PackageList) + Search(dep Dependency, allMatches bool) (searchResults []*Package) + SearchSupported() bool + SearchByKey(arch, name, version string) (result *PackageList) +} + // PackageQuery is interface of predicate on Package type PackageQuery interface { // Matches calculates match of condition against package Matches(pkg *Package) bool // Fast returns if search strategy is possible for this query - Fast() bool + Fast(list PackageCatalog) bool // Query performs search on package list - Query(list *PackageList) *PackageList + Query(list PackageCatalog) *PackageList // String interface String() string } @@ -60,13 +68,13 @@ func (q *OrQuery) Matches(pkg *Package) bool { } // Fast is true only if both parts are fast -func (q *OrQuery) Fast() bool { - return q.L.Fast() && q.R.Fast() +func (q *OrQuery) Fast(list PackageCatalog) bool { + return q.L.Fast(list) && q.R.Fast(list) } // Query strategy depends on nodes -func (q *OrQuery) Query(list *PackageList) (result *PackageList) { - if q.Fast() { +func (q *OrQuery) Query(list PackageCatalog) (result *PackageList) { + if q.Fast(list) { result = q.L.Query(list) result.Append(q.R.Query(list)) } else { @@ -86,16 +94,16 @@ func (q *AndQuery) Matches(pkg *Package) bool { } // Fast is true if any of the parts are fast -func (q *AndQuery) Fast() bool { - return q.L.Fast() || q.R.Fast() +func (q *AndQuery) Fast(list PackageCatalog) bool { + return q.L.Fast(list) || q.R.Fast(list) } // Query strategy depends on nodes -func (q *AndQuery) Query(list *PackageList) (result *PackageList) { - if !q.Fast() { +func (q *AndQuery) Query(list PackageCatalog) (result *PackageList) { + if !q.Fast(list) { result = list.Scan(q) } else { - if q.L.Fast() { + if q.L.Fast(list) { result = q.L.Query(list) result = result.Scan(q.R) } else { @@ -117,12 +125,12 @@ func (q *NotQuery) Matches(pkg *Package) bool { } // Fast is false -func (q *NotQuery) Fast() bool { +func (q *NotQuery) Fast(list PackageCatalog) bool { return false } // Query strategy is scan always -func (q *NotQuery) Query(list *PackageList) (result *PackageList) { +func (q *NotQuery) Query(list PackageCatalog) (result *PackageList) { result = list.Scan(q) return } @@ -170,13 +178,13 @@ func (q *FieldQuery) Matches(pkg *Package) bool { } // Query runs iteration through list -func (q *FieldQuery) Query(list *PackageList) (result *PackageList) { +func (q *FieldQuery) Query(list PackageCatalog) (result *PackageList) { result = list.Scan(q) return } // Fast depends on the query -func (q *FieldQuery) Fast() bool { +func (q *FieldQuery) Fast(list PackageCatalog) bool { return false } @@ -215,15 +223,19 @@ func (q *DependencyQuery) Matches(pkg *Package) bool { } // Fast is always true for dependency query -func (q *DependencyQuery) Fast() bool { - return true +func (q *DependencyQuery) Fast(list PackageCatalog) bool { + return list.SearchSupported() } // Query runs PackageList.Search -func (q *DependencyQuery) Query(list *PackageList) (result *PackageList) { - result = NewPackageList() - for _, pkg := range list.Search(q.Dep, true) { - result.Add(pkg) +func (q *DependencyQuery) Query(list PackageCatalog) (result *PackageList) { + if q.Fast(list) { + result = NewPackageList() + for _, pkg := range list.Search(q.Dep, true) { + result.Add(pkg) + } + } else { + result = list.Scan(q) } return @@ -240,20 +252,13 @@ func (q *PkgQuery) Matches(pkg *Package) bool { } // Fast is always true for package query -func (q *PkgQuery) Fast() bool { +func (q *PkgQuery) Fast(list PackageCatalog) bool { return true } // Query looks up specific package -func (q *PkgQuery) Query(list *PackageList) (result *PackageList) { - result = NewPackageList() - - pkg := list.packages["P"+q.Arch+" "+q.Pkg+" "+q.Version] - if pkg != nil { - result.Add(pkg) - } - - return +func (q *PkgQuery) Query(list PackageCatalog) (result *PackageList) { + return list.SearchByKey(q.Arch, q.Pkg, q.Version) } // String interface diff --git a/src/github.com/smira/aptly/deb/reflist.go b/src/github.com/smira/aptly/deb/reflist.go index 008316ea..7da58773 100644 --- a/src/github.com/smira/aptly/deb/reflist.go +++ b/src/github.com/smira/aptly/deb/reflist.go @@ -84,6 +84,14 @@ func (l *PackageRefList) ForEach(handler func([]byte) error) error { return err } +// Has checks whether package is part of reflist +func (l *PackageRefList) Has(p *Package) bool { + key := p.Key("") + + i := sort.Search(len(l.Refs), func(j int) bool { return bytes.Compare(l.Refs[j], key) >= 0 }) + return i < len(l.Refs) && bytes.Compare(l.Refs[i], key) == 0 +} + // Substract returns all packages in l that are not in r func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList { result := &PackageRefList{Refs: make([][]byte, 0, 128)} diff --git a/src/github.com/smira/aptly/deb/reflist_test.go b/src/github.com/smira/aptly/deb/reflist_test.go index 4a0dabb9..c1fdb4ec 100644 --- a/src/github.com/smira/aptly/deb/reflist_test.go +++ b/src/github.com/smira/aptly/deb/reflist_test.go @@ -128,6 +128,19 @@ func (s *PackageRefListSuite) TestPackageRefListForeach(c *C) { c.Check(err, Equals, e) } +func (s *PackageRefListSuite) TestHas(c *C) { + s.list.Add(s.p1) + s.list.Add(s.p3) + s.list.Add(s.p5) + reflist := NewPackageRefListFromPackageList(s.list) + + c.Check(reflist.Has(s.p1), Equals, true) + c.Check(reflist.Has(s.p3), Equals, true) + c.Check(reflist.Has(s.p5), Equals, true) + c.Check(reflist.Has(s.p2), Equals, true) + c.Check(reflist.Has(s.p6), Equals, false) +} + func (s *PackageRefListSuite) TestSubstract(c *C) { r1 := []byte("r1") r2 := []byte("r2") diff --git a/src/github.com/smira/aptly/deb/remote.go b/src/github.com/smira/aptly/deb/remote.go index 1d439d44..c109a535 100644 --- a/src/github.com/smira/aptly/deb/remote.go +++ b/src/github.com/smira/aptly/deb/remote.go @@ -16,9 +16,16 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) +// RemoteRepo statuses +const ( + MirrorIdle = iota + MirrorUpdating +) + // RemoteRepo represents remote (fetchable) Debian repository. // // Repostitory could be filtered when fetching by components, architectures @@ -37,6 +44,8 @@ type RemoteRepo struct { Architectures []string // Should we download sources? DownloadSources bool + // Should we download .udebs? + DownloadUdebs bool // Meta-information about repository Meta Stanza // Last update date @@ -47,15 +56,23 @@ type RemoteRepo struct { Filter string // FilterWithDeps to include dependencies from filter query FilterWithDeps bool + // Status marks state of repository (being updated, no action) + Status int + // WorkerPID is PID of the process modifying the mirror (if any) + WorkerPID int // "Snapshot" of current list of packages packageRefs *PackageRefList + // Temporary list of package refs + tempPackageRefs *PackageRefList // Parsed archived root archiveRootURL *url.URL + // Current list of packages (filled while updating mirror) + packageList *PackageList } // NewRemoteRepo creates new instance of Debian remote repository with specified params func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string, - architectures []string, downloadSources bool) (*RemoteRepo, error) { + architectures []string, downloadSources bool, downloadUdebs bool) (*RemoteRepo, error) { result := &RemoteRepo{ UUID: uuid.New(), Name: name, @@ -64,6 +81,7 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone Components: components, Architectures: architectures, DownloadSources: downloadSources, + DownloadUdebs: downloadUdebs, } err := result.prepare() @@ -80,6 +98,9 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone if len(result.Components) > 0 { return nil, fmt.Errorf("components aren't supported for flat repos") } + if result.DownloadUdebs { + return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos") + } result.Components = nil } @@ -102,7 +123,10 @@ func (repo *RemoteRepo) prepare() error { func (repo *RemoteRepo) String() string { srcFlag := "" if repo.DownloadSources { - srcFlag = " [src]" + srcFlag += " [src]" + } + if repo.DownloadUdebs { + srcFlag += " [udeb]" } distribution := repo.Distribution if distribution == "" { @@ -131,6 +155,37 @@ func (repo *RemoteRepo) RefList() *PackageRefList { return repo.packageRefs } +// MarkAsUpdating puts current PID and sets status to updating +func (repo *RemoteRepo) MarkAsUpdating() { + repo.Status = MirrorUpdating + repo.WorkerPID = os.Getpid() +} + +// MarkAsIdle clears updating flag +func (repo *RemoteRepo) MarkAsIdle() { + repo.Status = MirrorIdle + repo.WorkerPID = 0 +} + +// CheckLock returns error if mirror is being updated by another process +func (repo *RemoteRepo) CheckLock() error { + if repo.Status == MirrorIdle || repo.WorkerPID == 0 { + return nil + } + + p, err := os.FindProcess(repo.WorkerPID) + if err != nil { + return nil + } + + err = p.Signal(syscall.Signal(0)) + if err == nil { + return fmt.Errorf("mirror is locked by update operation, PID %d", repo.WorkerPID) + } + + return nil +} + // ReleaseURL returns URL to Release* files in repo root func (repo *RemoteRepo) ReleaseURL(name string) *url.URL { var path *url.URL @@ -169,6 +224,13 @@ func (repo *RemoteRepo) SourcesURL(component string) *url.URL { return repo.archiveRootURL.ResolveReference(path) } +// UdebURL returns URL of Packages files for given component and +// architecture +func (repo *RemoteRepo) UdebURL(component string, architecture string) *url.URL { + path := &url.URL{Path: fmt.Sprintf("dists/%s/%s/debian-installer/binary-%s/Packages", repo.Distribution, component, architecture)} + return repo.archiveRootURL.ResolveReference(path) +} + // PackageURL returns URL of package file relative to repository root // architecture func (repo *RemoteRepo) PackageURL(filename string) *url.URL { @@ -323,12 +385,13 @@ ok: return nil } -// Download downloads all repo files -func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory, - packagePool aptly.PackagePool, ignoreMismatch bool, dependencyOptions int, filterQuery PackageQuery) error { - list := NewPackageList() - - progress.Printf("Downloading & parsing package files...\n") +// DownloadPackageIndexes downloads & parses package index files +func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, collectionFactory *CollectionFactory, + ignoreMismatch bool) error { + if repo.packageList != nil { + panic("packageList != nil") + } + repo.packageList = NewPackageList() // Download and parse all Packages & Source files packagesURLs := [][]string{} @@ -342,6 +405,9 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co for _, component := range repo.Components { for _, architecture := range repo.Architectures { packagesURLs = append(packagesURLs, []string{repo.BinaryURL(component, architecture).String(), "binary"}) + if repo.DownloadUdebs { + packagesURLs = append(packagesURLs, []string{repo.UdebURL(component, architecture).String(), "udeb"}) + } } if repo.DownloadSources { packagesURLs = append(packagesURLs, []string{repo.SourcesURL(component).String(), "source"}) @@ -378,13 +444,15 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co if kind == "binary" { p = NewPackageFromControlFile(stanza) + } else if kind == "udeb" { + p = NewUdebPackageFromControlFile(stanza) } else if kind == "source" { p, err = NewSourcePackageFromControlFile(stanza) if err != nil { return err } } - err = list.Add(p) + err = repo.packageList.Add(p) if err != nil { return err } @@ -398,33 +466,30 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co progress.ShutdownBar() } - var err error + return nil +} - if repo.Filter != "" { - progress.Printf("Applying filter...\n") +// ApplyFilter applies filtering to already built PackageList +func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQuery) (oldLen, newLen int, err error) { + repo.packageList.PrepareIndex() - list.PrepareIndex() + emptyList := NewPackageList() + emptyList.PrepareIndex() - emptyList := NewPackageList() - emptyList.PrepareIndex() - - origPackages := list.Len() - list, err = list.Filter([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures) - if err != nil { - return err - } - - progress.Printf("Packages filtered: %d -> %d.\n", origPackages, list.Len()) + oldLen = repo.packageList.Len() + repo.packageList, err = repo.packageList.Filter([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures) + if repo.packageList != nil { + newLen = repo.packageList.Len() } + return +} - progress.Printf("Building download queue...\n") +// BuildDownloadQueue builds queue, discards current PackageList +func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool) (queue []PackageDownloadTask, downloadSize int64, err error) { + queue = make([]PackageDownloadTask, 0, repo.packageList.Len()) + seen := make(map[string]struct{}, repo.packageList.Len()) - // Build download queue - queued := make(map[string]PackageDownloadTask, list.Len()) - count := 0 - downloadSize := int64(0) - - err = list.ForEach(func(p *Package) error { + err = repo.packageList.ForEach(func(p *Package) error { list, err2 := p.DownloadList(packagePool) if err2 != nil { return err2 @@ -433,58 +498,31 @@ func (repo *RemoteRepo) Download(progress aptly.Progress, d aptly.Downloader, co for _, task := range list { key := task.RepoURI + "-" + task.DestinationPath - _, found := queued[key] + _, found := seen[key] if !found { - count++ + queue = append(queue, task) downloadSize += task.Checksums.Size - queued[key] = task + seen[key] = struct{}{} } } return nil }) if err != nil { - return fmt.Errorf("unable to build download queue: %s", err) + return } - repo.packageRefs = NewPackageRefListFromPackageList(list) + repo.tempPackageRefs = NewPackageRefListFromPackageList(repo.packageList) // free up package list, we don't need it after this point - list = nil + repo.packageList = nil - progress.Printf("Download queue: %d items (%s)\n", count, utils.HumanBytes(downloadSize)) - - progress.InitBar(downloadSize, true) - - // Download all package files - ch := make(chan error, len(queued)) - - for _, task := range queued { - d.DownloadWithChecksum(repo.PackageURL(task.RepoURI).String(), task.DestinationPath, ch, task.Checksums, ignoreMismatch) - } - - // We don't need queued after this point - queued = nil - - // Wait for all downloads to finish - errors := make([]string, 0) - - for count > 0 { - err = <-ch - if err != nil { - errors = append(errors, err.Error()) - } - count-- - } - - progress.ShutdownBar() - - if len(errors) > 0 { - return fmt.Errorf("download errors:\n %s\n", strings.Join(errors, "\n ")) - } + return +} +// FinalizeDownload swaps for final value of package refs +func (repo *RemoteRepo) FinalizeDownload() { repo.LastDownloadDate = time.Now() - - return nil + repo.packageRefs = repo.tempPackageRefs } // Encode does msgpack encoding of RemoteRepo diff --git a/src/github.com/smira/aptly/deb/remote_test.go b/src/github.com/smira/aptly/deb/remote_test.go index 206101dc..01ab3d42 100644 --- a/src/github.com/smira/aptly/deb/remote_test.go +++ b/src/github.com/smira/aptly/deb/remote_test.go @@ -12,6 +12,7 @@ import ( "io/ioutil" . "launchpad.net/gocheck" "os" + "sort" ) type NullVerifier struct { @@ -79,8 +80,8 @@ type RemoteRepoSuite struct { var _ = Suite(&RemoteRepoSuite{}) func (s *RemoteRepoSuite) SetUpTest(c *C) { - s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false) - s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false) + s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false) + s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false) s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) s.progress = console.NewProgress() s.db, _ = database.OpenDB(c.MkDir()) @@ -96,7 +97,7 @@ func (s *RemoteRepoSuite) TearDownTest(c *C) { } func (s *RemoteRepoSuite) TestInvalidURL(c *C) { - _, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false) + _, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false) c.Assert(err, ErrorMatches, ".*hexadecimal escape in host.*") } @@ -106,11 +107,11 @@ func (s *RemoteRepoSuite) TestFlatCreation(c *C) { c.Check(s.flat.Architectures, IsNil) c.Check(s.flat.Components, IsNil) - flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false) + flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false) c.Check(flat2.IsFlat(), Equals, true) c.Check(flat2.Distribution, Equals, "./binary/") - _, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false) + _, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false) c.Check(err, ErrorMatches, "components aren't supported for flat repos") } @@ -119,8 +120,9 @@ func (s *RemoteRepoSuite) TestString(c *C) { c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./") s.repo.DownloadSources = true + s.repo.DownloadUdebs = true s.flat.DownloadSources = true - c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src]") + c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb]") c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]") } @@ -151,6 +153,10 @@ func (s *RemoteRepoSuite) TestBinaryURL(c *C) { c.Assert(s.repo.BinaryURL("main", "amd64").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/binary-amd64/Packages") } +func (s *RemoteRepoSuite) TestUdebURL(c *C) { + c.Assert(s.repo.UdebURL("main", "amd64").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/debian-installer/binary-amd64/Packages") +} + func (s *RemoteRepoSuite) TestSourcesURL(c *C) { c.Assert(s.repo.SourcesURL("main").String(), Equals, "http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources") } @@ -209,13 +215,13 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) { } func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) { - s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false) + s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false) err := s.repo.Fetch(s.downloader, nil) c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*") } func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) { - s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false) + s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false) err := s.repo.Fetch(s.downloader, nil) c.Assert(err, ErrorMatches, "component xyz not available in repo.*") } @@ -249,20 +255,22 @@ func (s *RemoteRepoSuite) TestDownload(c *C) { s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404")) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) - s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz") - err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) + + queue, size, err := s.repo.BuildDownloadQueue(s.packagePool) + c.Check(size, Equals, int64(3)) + c.Check(queue, HasLen, 1) + c.Check(queue[0].RepoURI, Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb") + + s.repo.FinalizeDownload() c.Assert(s.repo.packageRefs, NotNil) pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0]) c.Assert(err, IsNil) - result, err := pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "amanda-client") } @@ -279,32 +287,35 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", errors.New("HTTP 404")) s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", errors.New("HTTP 404")) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) - s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz") - s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc") - s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd") - s.downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde") - err = s.repo.Download(s.progress, s.downloader, s.collectionFactory, s.packagePool, false, 0, nil) + err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) c.Assert(err, IsNil) c.Assert(s.downloader.Empty(), Equals, true) + + queue, size, err := s.repo.BuildDownloadQueue(s.packagePool) + c.Check(size, Equals, int64(15)) + c.Check(queue, HasLen, 4) + + q := make([]string, 4) + for i := range q { + q[i] = queue[i].RepoURI + } + sort.Strings(q) + c.Check(q[3], Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb") + c.Check(q[1], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc") + c.Check(q[2], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz") + c.Check(q[0], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz") + + s.repo.FinalizeDownload() c.Assert(s.repo.packageRefs, NotNil) pkg, err := s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[0]) c.Assert(err, IsNil) - result, err := pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "amanda-client") pkg, err = s.collectionFactory.PackageCollection().ByKey(s.repo.packageRefs.Refs[1]) c.Assert(err, IsNil) - - result, err = pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "access-modifier-checker") } @@ -314,23 +325,25 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404")) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) - downloader.ExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz") err := s.flat.Fetch(downloader, nil) c.Assert(err, IsNil) - err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, false) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) + + queue, size, err := s.flat.BuildDownloadQueue(s.packagePool) + c.Check(size, Equals, int64(3)) + c.Check(queue, HasLen, 1) + c.Check(queue[0].RepoURI, Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb") + + s.flat.FinalizeDownload() c.Assert(s.flat.packageRefs, NotNil) pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0]) c.Assert(err, IsNil) - result, err := pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "amanda-client") } @@ -345,35 +358,39 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) { downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", errors.New("HTTP 404")) downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", errors.New("HTTP 404")) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) - downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb", "xyz") - downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc", "abc") - downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz", "abcd") - downloader.AnyExpectResponse("http://repos.express42.com/virool/precise/pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz", "abcde") err := s.flat.Fetch(downloader, nil) c.Assert(err, IsNil) - err = s.flat.Download(s.progress, downloader, s.collectionFactory, s.packagePool, false, 0, nil) + err = s.flat.DownloadPackageIndexes(s.progress, downloader, s.collectionFactory, false) c.Assert(err, IsNil) c.Assert(downloader.Empty(), Equals, true) + + queue, size, err := s.flat.BuildDownloadQueue(s.packagePool) + c.Check(size, Equals, int64(15)) + c.Check(queue, HasLen, 4) + + q := make([]string, 4) + for i := range q { + q[i] = queue[i].RepoURI + } + sort.Strings(q) + c.Check(q[3], Equals, "pool/main/a/amanda/amanda-client_3.3.1-3~bpo60+1_amd64.deb") + c.Check(q[1], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.dsc") + c.Check(q[2], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0.orig.tar.gz") + c.Check(q[0], Equals, "pool/main/a/access-modifier-checker/access-modifier-checker_1.0-4.debian.tar.gz") + + s.flat.FinalizeDownload() c.Assert(s.flat.packageRefs, NotNil) pkg, err := s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[0]) c.Assert(err, IsNil) - result, err := pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "amanda-client") pkg, err = s.collectionFactory.PackageCollection().ByKey(s.flat.packageRefs.Refs[1]) c.Assert(err, IsNil) - result, err = pkg.VerifyFiles(s.packagePool) - c.Check(result, Equals, true) - c.Check(err, IsNil) - c.Check(pkg.Name, Equals, "access-modifier-checker") } @@ -399,7 +416,7 @@ func (s *RemoteRepoCollectionSuite) TestAddByName(c *C) { r, err := s.collection.ByName("yandex") c.Assert(err, ErrorMatches, "*.not found") - repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) c.Assert(s.collection.Add(repo), IsNil) c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists") @@ -417,7 +434,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) { r, err := s.collection.ByUUID("some-uuid") c.Assert(err, ErrorMatches, "*.not found") - repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) c.Assert(s.collection.Add(repo), IsNil) r, err = s.collection.ByUUID(repo.UUID) @@ -426,7 +443,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) { } func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) { - repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) c.Assert(s.collection.Update(repo), IsNil) collection := NewRemoteRepoCollection(s.db) @@ -447,7 +464,7 @@ func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) { } func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) { - repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) s.collection.Add(repo) count := 0 @@ -469,10 +486,10 @@ func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) { } func (s *RemoteRepoCollectionSuite) TestDrop(c *C) { - repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) s.collection.Add(repo1) - repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false) + repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false) s.collection.Add(repo2) r1, _ := s.collection.ByUUID(repo1.UUID) diff --git a/src/github.com/smira/aptly/deb/snapshot_test.go b/src/github.com/smira/aptly/deb/snapshot_test.go index 9d52d713..83db0853 100644 --- a/src/github.com/smira/aptly/deb/snapshot_test.go +++ b/src/github.com/smira/aptly/deb/snapshot_test.go @@ -15,7 +15,7 @@ var _ = Suite(&SnapshotSuite{}) func (s *SnapshotSuite) SetUpTest(c *C) { s.SetUpPackages() - s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) s.repo.packageRefs = s.reflist } @@ -108,11 +108,11 @@ func (s *SnapshotCollectionSuite) SetUpTest(c *C) { s.collection = NewSnapshotCollection(s.db) s.SetUpPackages() - s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false) + s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false) s.repo1.packageRefs = s.reflist s.snapshot1, _ = NewSnapshotFromRepository("snap1", s.repo1) - s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false) + s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false) s.repo2.packageRefs = s.reflist s.snapshot2, _ = NewSnapshotFromRepository("snap2", s.repo2) @@ -192,7 +192,7 @@ func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) { c.Check(s.collection.ByRemoteRepoSource(s.repo1), DeepEquals, []*Snapshot{s.snapshot1}) c.Check(s.collection.ByRemoteRepoSource(s.repo2), DeepEquals, []*Snapshot{s.snapshot2}) - repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false) + repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false) c.Check(s.collection.ByRemoteRepoSource(repo3), DeepEquals, []*Snapshot{}) } diff --git a/src/github.com/smira/aptly/http/download.go b/src/github.com/smira/aptly/http/download.go index c0ae4e16..adf3c914 100644 --- a/src/github.com/smira/aptly/http/download.go +++ b/src/github.com/smira/aptly/http/download.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/smira/aptly/aptly" "github.com/smira/aptly/utils" + "github.com/smira/go-ftp-protocol/protocol" "io" "io/ioutil" "net/http" @@ -47,11 +48,12 @@ type downloadTask struct { func NewDownloader(threads int, downLimit int64, progress aptly.Progress) aptly.Downloader { transport := *http.DefaultTransport.(*http.Transport) transport.DisableCompression = true + transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{}) downloader := &downloaderImpl{ queue: make(chan *downloadTask, 1000), - stop: make(chan struct{}), - stopped: make(chan struct{}), + stop: make(chan struct{}, threads), + stopped: make(chan struct{}, threads), pause: make(chan struct{}), unpause: make(chan struct{}), threads: threads, @@ -86,6 +88,13 @@ func (downloader *downloaderImpl) Shutdown() { } } +// Abort stops downloader but doesn't wait for downloader to stop +func (downloader *downloaderImpl) Abort() { + for i := 0; i < downloader.threads; i++ { + downloader.stop <- struct{}{} + } +} + // Pause pauses task processing func (downloader *downloaderImpl) Pause() { for i := 0; i < downloader.threads; i++ { @@ -122,10 +131,12 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { resp, err := downloader.client.Get(task.url) if err != nil { - task.result <- err + task.result <- fmt.Errorf("%s: %s", task.url, err) return } - defer resp.Body.Close() + if resp.Body != nil { + defer resp.Body.Close() + } if resp.StatusCode < 200 || resp.StatusCode > 299 { task.result <- fmt.Errorf("HTTP code %d while fetching %s", resp.StatusCode, task.url) @@ -134,7 +145,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { err = os.MkdirAll(filepath.Dir(task.destination), 0755) if err != nil { - task.result <- err + task.result <- fmt.Errorf("%s: %s", task.url, err) return } @@ -142,7 +153,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { outfile, err := os.Create(temppath) if err != nil { - task.result <- err + task.result <- fmt.Errorf("%s: %s", task.url, err) return } defer outfile.Close() @@ -159,7 +170,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { _, err = io.Copy(w, resp.Body) if err != nil { os.Remove(temppath) - task.result <- err + task.result <- fmt.Errorf("%s: %s", task.url, err) return } @@ -190,7 +201,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { err = os.Rename(temppath, task.destination) if err != nil { os.Remove(temppath) - task.result <- err + task.result <- fmt.Errorf("%s: %s", task.url, err) return } diff --git a/src/github.com/smira/aptly/http/fake.go b/src/github.com/smira/aptly/http/fake.go index 4d256a3f..c2eda915 100644 --- a/src/github.com/smira/aptly/http/fake.go +++ b/src/github.com/smira/aptly/http/fake.go @@ -123,6 +123,10 @@ func (f *FakeDownloader) Download(url string, filename string, result chan<- err func (f *FakeDownloader) Shutdown() { } +// Abort does nothing +func (f *FakeDownloader) Abort() { +} + // Pause does nothing func (f *FakeDownloader) Pause() { } diff --git a/src/github.com/smira/aptly/http/http.go b/src/github.com/smira/aptly/http/http.go index f6fb5be4..84b2cec2 100644 --- a/src/github.com/smira/aptly/http/http.go +++ b/src/github.com/smira/aptly/http/http.go @@ -1,2 +1,2 @@ -// Package http provides all HTTP-related operations +// Package http provides all HTTP (and FTP)-related operations package http diff --git a/src/github.com/smira/aptly/main.go b/src/github.com/smira/aptly/main.go index 8bb4bc91..47d3b690 100644 --- a/src/github.com/smira/aptly/main.go +++ b/src/github.com/smira/aptly/main.go @@ -1,38 +1,10 @@ package main import ( - "fmt" "github.com/smira/aptly/cmd" "os" ) func main() { - defer func() { - if r := recover(); r != nil { - fatal, ok := r.(*cmd.FatalError) - if !ok { - panic(r) - } - fmt.Println("ERROR:", fatal.Message) - os.Exit(fatal.ReturnCode) - } - }() - - command := cmd.RootCommand() - - flags, args, err := command.ParseFlags(os.Args[1:]) - if err != nil { - cmd.Fatal(err) - } - - err = cmd.InitContext(flags) - if err != nil { - cmd.Fatal(err) - } - defer cmd.ShutdownContext() - - err = command.Dispatch(args) - if err != nil { - cmd.Fatal(err) - } + os.Exit(cmd.Run(cmd.RootCommand(), os.Args[1:], true)) } diff --git a/src/github.com/smira/aptly/man/aptly.1 b/src/github.com/smira/aptly/man/aptly.1 index c64d5566..642ade7f 100644 --- a/src/github.com/smira/aptly/man/aptly.1 +++ b/src/github.com/smira/aptly/man/aptly.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "APTLY" "1" "August 2014" "" "" +.TH "APTLY" "1" "October 2014" "" "" . .SH "NAME" \fBaptly\fR \- Debian repository management tool @@ -22,10 +22,10 @@ aptly has integrated help that matches contents of this manual page, to get help aptly is a tool to create partial and full mirrors of remote repositories, manage local repositories, filter them, merge, upgrade individual packages, take snapshots and publish them back as Debian repositories\. . .P -aptly\(cqs goal is to establish repeatability and controlled changes in a package\-centric environment\. aptly allows to fix a set of packages in a repository, so that package installation and upgrade becomes deterministic\. At the same time aptly allows to perform controlled, fine\-grained changes in repository contents to transition your package environment to new version\. +aptly\(cqs goal is to establish repeatability and controlled changes in a package\-centric environment\. aptly allows one to fix a set of packages in a repository, so that package installation and upgrade becomes deterministic\. At the same time aptly allows one to perform controlled, fine\-grained changes in repository contents to transition your package environment to new version\. . .SH "CONFIGURATION" -aptly looks for configuration file in \fB/etc/aptly\.conf\fR and \fB~/\.aptly\.conf\fR, if no config file found, new one is created\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly\fR, it will be created if missing\. +aptly looks for configuration file first in \fB~/\.aptly\.conf\fR then in \fB/etc/aptly\.conf\fR and, if no config file found, new one is created in home directory\. If \fB\-config=\fR flag is specified, aptly would use config file at specified location\. Also aptly needs root directory for database, package and published repository storage\. If not specified, directory defaults to \fB~/\.aptly\fR, it will be created if missing\. . .P Configuration file is stored in JSON format (default values shown below): @@ -55,7 +55,10 @@ Configuration file is stored in JSON format (default values shown below): "awsAccessKeyID": "" "awsSecretAccessKey": "", "prefix": "", - "acl": "public\-read" + "acl": "public\-read", + "storageClass": "", + "encryptionMethod": "", + "plusWorkaround": false } } . @@ -115,7 +118,7 @@ if enabled, all mirrors created would have flag set to download source packages; specifies paramaters for short PPA url expansion, if left blank they default to output of \fBlsb_release\fR command . .TP -\fBS3PublisEndpoints\fR +\fBS3PublishEndpoints\fR configuration of Amazon S3 publishing endpoints (see below) . .SH "S3 PUBLISHING ENDPOINTS" @@ -141,6 +144,18 @@ bucket name \fBawsAccessKeyID\fR, \fBawsSecretAccessKey\fR (optional) Amazon credentials to access S3 bucket\. If not supplied, environment variables \fBAWS_ACCESS_KEY_ID\fR and \fBAWS_SECRET_ACCESS_KEY\fR are used\. . +.TP +\fBstorageClass\fR +(optional) Amazon S3 storage class, defaults to \fBSTANDARD\fR\. Other values available: \fBREDUCED_REDUNDANCY\fR (lower price, lower redundancy) +. +.TP +\fBencryptionMethod\fR +(optional) server\-side encryption method, defaults to none\. Currently the only available encryption method is \fBAES256\fR +. +.TP +\fBplusWorkaround\fR +(optional) workaround misbehavior in apt and Amazon S3 for files with \fB+\fR in filename by creating two copies of package files with \fB+\fR in filename: one original and another one with spaces instead of plus signs With \fBplusWorkaround\fR enabled, package files with plus sign would be stored twice\. aptly might not cleanup files with spaces when published repository is dropped or updated (switched) to new version of repository (snapshot)\. +. .P In order to publish to S3, specify endpoint as \fBs3:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.: . @@ -274,7 +289,7 @@ when processing dependencies, follow Suggests \fBaptly\fR \fBmirror\fR \fBcreate\fR \fIname\fR \fIarchive url\fR \fIdistribution\fR [\fIcomponent1\fR \|\.\|\.\|\.] . .P -Creates mirror \fIname\fR of remote repository, aptly supports both regular and flat Debian repositories exported via HTTP\. aptly would try download Release file from remote repository and verify its\(cq signature\. Command line format resembles apt utlitily sources\.list(5)\. +Creates mirror \fIname\fR of remote repository, aptly supports both regular and flat Debian repositories exported via HTTP and FTP\. aptly would try download Release file from remote repository and verify its\(cq signature\. Command line format resembles apt utlitily sources\.list(5)\. . .P PPA urls could specified in short format: @@ -311,6 +326,10 @@ gpg keyring to use when verifying Release file (could be specified multiple time \-\fBwith\-sources\fR=false download source packages in addition to binary packages . +.TP +\-\fBwith\-udebs\fR=false +download \.udeb packages (Debian installer support) +. .SH "LIST MIRRORS" \fBaptly\fR \fBmirror\fR \fBlist\fR . @@ -388,6 +407,10 @@ Options: limit download speed (kbytes/sec) . .TP +\-\fBforce\fR=false +force update mirror even if it is locked by another process +. +.TP \-\fBignore\-checksums\fR=false ignore checksum mismatches while downloading package files and metadata . @@ -411,11 +434,11 @@ Example: .P $ aptly mirror rename wheezy\-min wheezy\-main . -.SH "EDIT PROPERTIES OF MIRORR" +.SH "EDIT MIRROR SETTINGS" \fBaptly\fR \fBmirror\fR \fBedit\fR \fIname\fR . .P -Command edit allows to change settings of mirror: filters\. +Command edit allows one to change settings of mirror: filters, list of architectures\. . .P Example: @@ -434,11 +457,45 @@ filter packages in mirror \-\fBfilter\-with\-deps\fR=false when filtering, include dependencies of matching packages as well . +.TP +\-\fBwith\-sources\fR=false +download source packages in addition to binary packages +. +.TP +\-\fBwith\-udebs\fR=false +download \.udeb packages (Debian installer support) +. +.SH "SEARCH MIRROR FOR PACKAGES MATCHING QUERY" +\fBaptly\fR \fBmirror\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR +. +.P +Command search displays list of packages in mirror that match package query +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly mirror search wheezy\-main \(cq$Architecture (i386), Name (% *\-dev)\(cq +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBwith\-deps\fR=false +include dependencies into search results +. .SH "ADD PACKAGES TO LOCAL REPOSITORY" \fBaptly\fR \fBrepo\fR \fBadd\fR \fIname\fR . .P -Command adds packages to local repository from \.deb (binary packages) and \.dsc (source packages) files\. When importing from directory aptly would do recursive scan looking for all files matching \fI\.deb or\fR\.dsc patterns\. Every file discovered would be analyzed to extract metadata, package would then be created and added to the database\. Files would be imported to internal package pool\. For source packages, all required files are added automatically as well\. Extra files for source package should be in the same directory as *\.dsc file\. +Command adds packages to local repository from \.deb, \.udeb (binary packages) and \.dsc (source packages) files\. When importing from directory aptly would do recursive scan looking for all files matching \fI\.[u]deb or\fR\.dsc patterns\. Every file discovered would be analyzed to extract metadata, package would then be created and added to the database\. Files would be imported to internal package pool\. For source packages, all required files are added automatically as well\. Extra files for source package should be in the same directory as *\.dsc file\. . .P Example: @@ -450,6 +507,10 @@ $ aptly repo add testing myapp\-0\.1\.2\.deb incoming/ Options: . .TP +\-\fBforce\-replace\fR=false +when adding package that conflicts with existing package, remove existing package +. +.TP \-\fBremove\-files\fR=false remove files that have been imported successfully into repository . @@ -526,7 +587,7 @@ force local repo deletion even if used by snapshots \fBaptly\fR \fBrepo\fR \fBedit\fR \fIname\fR . .P -Command edit allows to change metadata of local repository: comment, default distribution and component\. +Command edit allows one to change metadata of local repository: comment, default distribution and component\. . .P Example: @@ -661,6 +722,32 @@ Example: .P $ aptly repo rename wheezy\-min wheezy\-main . +.SH "SEARCH REPO FOR PACKAGES MATCHING QUERY" +\fBaptly\fR \fBrepo\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR +. +.P +Command search displays list of packages in local repository that match package query +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly repo search my\-software \(cq$Architecture (i386), Name (% *\-dev)\(cq +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBwith\-deps\fR=false +include dependencies into search results +. .SH "CREATES SNAPSHOT OF MIRROR (LOCAL REPOSITORY) CONTENTS" \fBaptly\fR \fBsnapshot\fR \fBcreate\fR \fIname\fR \fBfrom\fR \fBmirror\fR \fImirror\-name\fR \fB|\fR \fBfrom\fR \fBrepo\fR \fIrepo\-name\fR \fB|\fR \fBempty\fR . @@ -879,6 +966,58 @@ Example: .P $ aptly snapshot rename wheezy\-min wheezy\-main . +.SH "SEARCH SNAPSHOT FOR PACKAGES MATCHING QUERY" +\fBaptly\fR \fBsnapshot\fR \fBsearch\fR \fIname\fR \fIpackage\-query\fR +. +.P +Command search displays list of packages in snapshot that match package query +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly snapshot search wheezy\-main \(cq$Architecture (i386), Name (% *\-dev)\(cq +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBwith\-deps\fR=false +include dependencies into search results +. +.SH "FILTER PACKAGES IN SNAPSHOT PRODUCING ANOTHER SNAPSHOT" +\fBaptly\fR \fBsnapshot\fR \fBfilter\fR \fIsource\fR \fIdestination\fR \fIpackage\-query\fR \fB\|\.\|\.\|\.\fR +. +.P +Command filter does filtering in snapshot \fIsource\fR, producing another snapshot \fIdestination\fR\. Packages could be specified simply as \(cqpackage\-name\(cq or as package queries\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly snapshot filter wheezy\-main wheezy\-required \(cqPriorioty (required)\(cq +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBwith\-deps\fR=false +include dependent packages as well +. .SH "REMOVE PUBLISHED REPOSITORY" \fBaptly\fR \fBpublish\fR \fBdrop\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] . @@ -991,6 +1130,14 @@ label to publish origin name to publish . .TP +\-\fBpassphrase\fR= +GPG passhprase for the key (warning: could be insecure) +. +.TP +\-\fBpassphrase\-file\fR= +GPG passhprase\-file for the key (warning: could be insecure) +. +.TP \-\fBsecret\-keyring\fR= GPG secret keyring to use (instead of default) . @@ -1062,6 +1209,14 @@ label to publish origin name to publish . .TP +\-\fBpassphrase\fR= +GPG passhprase for the key (warning: could be insecure) +. +.TP +\-\fBpassphrase\-file\fR= +GPG passhprase\-file for the key (warning: could be insecure) +. +.TP \-\fBsecret\-keyring\fR= GPG secret keyring to use (instead of default) . @@ -1121,6 +1276,14 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBpassphrase\fR= +GPG passhprase for the key (warning: could be insecure) +. +.TP +\-\fBpassphrase\-file\fR= +GPG passhprase\-file for the key (warning: could be insecure) +. +.TP \-\fBsecret\-keyring\fR= GPG secret keyring to use (instead of default) . @@ -1166,6 +1329,14 @@ GPG key ID to use when signing the release GPG keyring to use (instead of default) . .TP +\-\fBpassphrase\fR= +GPG passhprase for the key (warning: could be insecure) +. +.TP +\-\fBpassphrase\-file\fR= +GPG passhprase\-file for the key (warning: could be insecure) +. +.TP \-\fBsecret\-keyring\fR= GPG secret keyring to use (instead of default) . @@ -1173,6 +1344,55 @@ GPG secret keyring to use (instead of default) \-\fBskip\-signing\fR=false don\(cqt sign Release files with GPG . +.SH "SEARCH FOR PACKAGES MATCHING QUERY" +\fBaptly\fR \fBpackage\fR \fBsearch\fR \fIpackage\-query\fR +. +.P +Command search displays list of packages in whole DB that match package query +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly package search \(cq$Architecture (i386), Name (% *\-dev)\(cq +. +.fi +. +.IP "" 0 +. +.SH "SHOW DETAILS ABOUT PACKAGES MATCING QUERY" +\fBaptly\fR \fBpackage\fR \fBshow\fR \fIpackage\-query\fR +. +.P +Command shows displays detailed meta\-information about packages matching query\. Information from Debian control file is displayed\. Optionally information about package files and inclusion into mirrors/snapshots/local repos is shown\. +. +.P +Example: +. +.IP "" 4 +. +.nf + +$ aptly package show nginx\-light_1\.2\.1\-2\.2+wheezy2_i386\(cq +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBwith\-files\fR=false +display information about files from package pool +. +.TP +\-\fBwith\-references\fR=false +display information about mirrors, snapshots and local repos referencing this package +. .SH "CLEANUP DB AND PACKAGE POOL" \fBaptly\fR \fBdb\fR \fBcleanup\fR . diff --git a/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl b/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl index db7f17c0..b2674547 100644 --- a/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl +++ b/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl @@ -18,8 +18,9 @@ aptly has integrated help that matches contents of this manual page, to get help ## CONFIGURATION -aptly looks for configuration file in `/etc/aptly.conf` and `~/.aptly.conf`, if no config file -found, new one is created. If `-config=` flag is specified, aptly would use config file at specified +aptly looks for configuration file first in `~/.aptly.conf` then +in `/etc/aptly.conf` and, if no config file found, new one is created in +home directory. If `-config=` flag is specified, aptly would use config file at specified location. Also aptly needs root directory for database, package and published repository storage. If not specified, directory defaults to `~/.aptly`, it will be created if missing. @@ -46,7 +47,10 @@ Configuration file is stored in JSON format (default values shown below): "awsAccessKeyID": "" "awsSecretAccessKey": "", "prefix": "", - "acl": "public-read" + "acl": "public-read", + "storageClass": "", + "encryptionMethod": "", + "plusWorkaround": false } } @@ -94,7 +98,7 @@ Options: specifies paramaters for short PPA url expansion, if left blank they default to output of `lsb_release` command - * `S3PublisEndpoints`: + * `S3PublishEndpoints`: configuration of Amazon S3 publishing endpoints (see below) ## S3 PUBLISHING ENDPOINTS @@ -120,6 +124,20 @@ and associated settings: (optional) Amazon credentials to access S3 bucket. If not supplied, environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are used. + * `storageClass`: + (optional) Amazon S3 storage class, defaults to `STANDARD`. Other values + available: `REDUCED_REDUNDANCY` (lower price, lower redundancy) + * `encryptionMethod`: + (optional) server-side encryption method, defaults to none. Currently + the only available encryption method is `AES256` + * `plusWorkaround`: + (optional) workaround misbehavior in apt and Amazon S3 + for files with `+` in filename by + creating two copies of package files with `+` in filename: one original + and another one with spaces instead of plus signs + With `plusWorkaround` enabled, package files with plus sign + would be stored twice. aptly might not cleanup files with spaces when published + repository is dropped or updated (switched) to new version of repository (snapshot). In order to publish to S3, specify endpoint as `s3:endpoint-name:` before publishing prefix on the command line, e.g.: @@ -219,6 +237,8 @@ When specified on command line, query may have to be quoted according to shell r {{template "command" findCommand . "publish"}} +{{template "command" findCommand . "package"}} + {{template "command" findCommand . "db"}} {{template "command" findCommand . "serve"}} diff --git a/src/github.com/smira/aptly/s3/public.go b/src/github.com/smira/aptly/s3/public.go index 08a8cd6e..f0011127 100644 --- a/src/github.com/smira/aptly/s3/public.go +++ b/src/github.com/smira/aptly/s3/public.go @@ -13,10 +13,13 @@ import ( // PublishedStorage abstract file system with published files (actually hosted on S3) type PublishedStorage struct { - s3 *s3.S3 - bucket *s3.Bucket - acl s3.ACL - prefix string + s3 *s3.S3 + bucket *s3.Bucket + acl s3.ACL + prefix string + storageClass string + encryptionMethod string + plusWorkaround bool } // Check interface @@ -25,12 +28,23 @@ var ( ) // NewPublishedStorageRaw creates published storage from raw aws credentials -func NewPublishedStorageRaw(auth aws.Auth, region aws.Region, bucket, defaultACL, prefix string) (*PublishedStorage, error) { +func NewPublishedStorageRaw(auth aws.Auth, region aws.Region, bucket, defaultACL, prefix, + storageClass, encryptionMethod string, plusWorkaround bool) (*PublishedStorage, error) { if defaultACL == "" { defaultACL = "private" } - result := &PublishedStorage{s3: s3.New(auth, region), acl: s3.ACL(defaultACL), prefix: prefix} + if storageClass == "STANDARD" { + storageClass = "" + } + + result := &PublishedStorage{ + s3: s3.New(auth, region), + acl: s3.ACL(defaultACL), + prefix: prefix, + storageClass: storageClass, + encryptionMethod: encryptionMethod, + plusWorkaround: plusWorkaround} result.bucket = result.s3.Bucket(bucket) return result, nil @@ -38,7 +52,8 @@ func NewPublishedStorageRaw(auth aws.Auth, region aws.Region, bucket, defaultACL // NewPublishedStorage creates new instance of PublishedStorage with specified S3 access // keys, region and bucket name -func NewPublishedStorage(accessKey, secretKey, region, bucket, defaultACL, prefix string) (*PublishedStorage, error) { +func NewPublishedStorage(accessKey, secretKey, region, bucket, defaultACL, prefix, + storageClass, encryptionMethod string, plusWorkaround bool) (*PublishedStorage, error) { auth, err := aws.GetAuth(accessKey, secretKey) if err != nil { return nil, err @@ -49,7 +64,7 @@ func NewPublishedStorage(accessKey, secretKey, region, bucket, defaultACL, prefi return nil, fmt.Errorf("unknown region: %#v", region) } - return NewPublishedStorageRaw(auth, awsRegion, bucket, defaultACL, prefix) + return NewPublishedStorageRaw(auth, awsRegion, bucket, defaultACL, prefix, storageClass, encryptionMethod, plusWorkaround) } // String @@ -81,10 +96,24 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err return err } - err = storage.bucket.PutReader(filepath.Join(storage.prefix, path), source, fi.Size(), "binary/octet-stream", storage.acl) + headers := map[string][]string{ + "Content-Type": {"binary/octet-stream"}, + } + if storage.storageClass != "" { + headers["x-amz-storage-class"] = []string{storage.storageClass} + } + if storage.encryptionMethod != "" { + headers["x-amz-server-side-encryption"] = []string{storage.encryptionMethod} + } + + err = storage.bucket.PutReaderHeader(filepath.Join(storage.prefix, path), source, fi.Size(), headers, storage.acl) if err != nil { return fmt.Errorf("error uploading %s to %s: %s", sourceFilename, storage, err) } + + if storage.plusWorkaround && strings.Index(path, "+") != -1 { + return storage.PutFile(strings.Replace(path, "+", " ", -1), sourceFilename) + } return nil } diff --git a/src/github.com/smira/aptly/s3/public_test.go b/src/github.com/smira/aptly/s3/public_test.go index 8d54be3a..d2ab8ee0 100644 --- a/src/github.com/smira/aptly/s3/public_test.go +++ b/src/github.com/smira/aptly/s3/public_test.go @@ -24,10 +24,10 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { c.Assert(s.srv, NotNil) auth, _ := aws.GetAuth("aa", "bb") - s.storage, err = NewPublishedStorageRaw(auth, aws.Region{Name: "test-1", S3Endpoint: s.srv.URL(), S3LocationConstraint: true}, "test", "", "") + s.storage, err = NewPublishedStorageRaw(auth, aws.Region{Name: "test-1", S3Endpoint: s.srv.URL(), S3LocationConstraint: true}, "test", "", "", "", "", false) c.Assert(err, IsNil) - s.prefixedStorage, err = NewPublishedStorageRaw(auth, aws.Region{Name: "test-1", S3Endpoint: s.srv.URL(), S3LocationConstraint: true}, "test", "", "lala") + s.prefixedStorage, err = NewPublishedStorageRaw(auth, aws.Region{Name: "test-1", S3Endpoint: s.srv.URL(), S3LocationConstraint: true}, "test", "", "lala", "", "", false) c.Assert(err, IsNil) err = s.storage.s3.Bucket("test").PutBucket("private") @@ -39,7 +39,7 @@ func (s *PublishedStorageSuite) TearDownTest(c *C) { } func (s *PublishedStorageSuite) TestNewPublishedStorage(c *C) { - stor, err := NewPublishedStorage("aa", "bbb", "", "", "", "") + stor, err := NewPublishedStorage("aa", "bbb", "", "", "", "", "", "", false) c.Check(stor, IsNil) c.Check(err, ErrorMatches, "unknown region: .*") } @@ -64,6 +64,25 @@ func (s *PublishedStorageSuite) TestPutFile(c *C) { c.Check(data, DeepEquals, []byte("welcome to s3!")) } +func (s *PublishedStorageSuite) TestPutFilePlusWorkaround(c *C) { + s.storage.plusWorkaround = true + + dir := c.MkDir() + err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("welcome to s3!"), 0644) + c.Assert(err, IsNil) + + err = s.storage.PutFile("a/b+c.txt", filepath.Join(dir, "a")) + c.Check(err, IsNil) + + data, err := s.storage.bucket.Get("a/b+c.txt") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("welcome to s3!")) + + data, err = s.storage.bucket.Get("a/b c.txt") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("welcome to s3!")) +} + func (s *PublishedStorageSuite) TestFilelist(c *C) { paths := []string{"a", "b", "c", "testa", "test/a", "test/b", "lala/a", "lala/b", "lala/c"} for _, path := range paths { diff --git a/src/github.com/smira/aptly/system/files/aptly_passphrase.pub b/src/github.com/smira/aptly/system/files/aptly_passphrase.pub new file mode 100644 index 0000000000000000000000000000000000000000..ec24aa32d580f5d8b194894520b34acca0a273da GIT binary patch literal 915 zcmV;E18n@60ipy{0QiRy1OSd4WNFxt1~o!-S1@skL_54;9Geppna?(rQ9M6;^61m0 zTSc%gL&7ChBb7d0Or!C1EV9=15fqC^yng{ATtzFu#5gJRR62G-ad;H|u8fW1KTwD| zCDAtEgtlGHCcZom-PUPIqgO@njh~(Gx=D-y-tNOz))K;%IhEXR8>;}I-q#?f(6RWI z-OXcvP^lCq*Y(mz1OSYhefdk_v|koHb|0LjoxH?qTlgA$bn7b870Rh(E1cglq~G{> zWL?y~4}83gH#RycF+D%8ydsilZBTai=zl~H98lZSjS(v;$SGmeyYy8*$-j|mLziOa z{obdM51&ogV40AocwqWy+AlT_X#AfTRq$3*Tw5Ulo75{ z5GH^K+7jr|XFr#NX?6%so!OiZh!Wm`05$wkSQradtZH3SskB4Zw%f(MlI2R-7a=l+ z{8{$?w!RKVk-wSPG5bS^_ZEKw8Ne}Uuq?ynS-DDNnLJuM7R5qGzPE3U5p_#<2X?R+ zuJ3`wrlz5JMxYP|v@Jn!bZmJbRAqB?WpW@WWN&UKbRczeWguyEDIh#_Wpi{uVQ_S8 zc`j*gW^X=-VgwTr0stZf0#pF_hXNZT1`7!Y2Ll2I6$kfGlbf zPQGN+gxEe1^{B*lQvX=Ds$?0p8=xACrv5hEUyUMX#wm|s_X7X}1_S`N5*%@3y6^74 z;EF*YS!LmMi4Gt({SiHgpC!7P5z)S3^=$Wb)i;Na;L}$HUX!z_VwFNdy=X0ssjG0#pF_hXNZ60162Z^A3!;&E6{bVvhizhW1^F pQh@M!p0s?4b08k6=4vgD0G>G%4Dudkh>m4~)&`HDtX0QWgp6=$od5s; literal 0 HcmV?d00001 diff --git a/src/github.com/smira/aptly/system/files/aptly_passphrase.sec b/src/github.com/smira/aptly/system/files/aptly_passphrase.sec new file mode 100644 index 0000000000000000000000000000000000000000..2ffe24ef89c4e7d10037073e8862f7503f8618cc GIT binary patch literal 1052 zcmV+%1mpXa0pSEx0QiRy1OSd4WNFxt1~o!-S1@skL_54;9Geppna?(rQ9M6;^61m0 zTSc%gL&7ChBb7d0Or!C1EV9=15fqC^yng{ATtzFu#5gJRR62G-ad;H|u8fW1KTwD| zCDAtEgtlGHCcZom-PUPIqgO@njh~(Gx=D-y-tNOz))K;%IhEXR8>;}I-q#?f(6RWI z-OXcvP^lCq*Y(mz1OSYhefdk_v|koHb|0LjoxH?qTlgA$bn7b870Rh(E1cglq~G{> zWL?y~4}83gH#RycF+D%8ydsilZBTai=zl~H98lZSjS(v;$SGmeyYy8*$-j|mLziOa z{obdM51&ogV40AocwqWy+AlT_X#AfTRq$3*Tw5Ulo75{ z5GH^K+7jr|XFr#NX?6%so!OiZh!Wm`05$wkSQradtZH3SskB4Zw%f(MlI2R-7a=l+ z{8{$?w!RKVk-wSPG5bS^_ZEKw8Ne}Uuq?ynS-DDNnLJuM7R5qGzPE3U5p_#<2X?R+ zuJ3`wrlz5JMxYP|{sRL7D=XB?HUmj|U^$$e&t2|=B_YXad=mkJ=kX&cY2vWjGN3nB zD-j=-J1wYJT@3T@DFIFtwk?epcH;rGEkST}Yyg0vjU+3ke7Z0|EvW2m%QT3j`Jd z0|5da0Rk6*0162Z^A3!;&E6{bMJNEA4<(}n&Y3^*){v!(-Bs8-C|2#O0HCM&2wQDL zL1o(Y!zdTRXwe3#C+wX8R|Hf5_=gY#0O}1zeu71#Pm+V%W03BEpY+~@YNsmzLu}9< z&~xP=E_?QwN@)RxScujUPBN7AA^+HG%i6s$B3mGKZ5tj*`G&8FiOB@0g)rSHZ@?Eg zK@T)4WW2ZO_6d|uzGT#d*gg>TsKje>Z zf0g#1l9hhR0AF)>qD$GcIyY`KhK&{VxJh?n50SC%vbZd5af68kggg`TG9C524rc0esPistbb8{ZDgdC~ W)GO}0JmQ4yFg1%k_1g1bkD$UqR@;aG literal 0 HcmV?d00001 diff --git a/src/github.com/smira/aptly/system/lib.py b/src/github.com/smira/aptly/system/lib.py index a68d4cc2..e3e84f6a 100644 --- a/src/github.com/smira/aptly/system/lib.py +++ b/src/github.com/smira/aptly/system/lib.py @@ -155,6 +155,7 @@ class BaseTest(object): if not hasattr(command, "__iter__"): params = { 'files': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files"), + 'udebs': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "udebs"), 'testfiles': os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.__class__.__name__), } if self.fixtureWebServer: @@ -195,17 +196,21 @@ class BaseTest(object): self.verify_match(self.get_gold(), self.output, match_prepare=self.outputMatchPrepare) except: if self.captureResults: + if self.outputMatchPrepare is not None: + self.output = self.outputMatchPrepare(self.output) with open(self.get_gold_filename(), "w") as f: f.write(self.output) else: raise def check_cmd_output(self, command, gold_name, match_prepare=None, expected_code=0): + output = self.run_cmd(command, expected_code=expected_code) try: - output = self.run_cmd(command, expected_code=expected_code) self.verify_match(self.get_gold(gold_name), output, match_prepare) except: if self.captureResults: + if match_prepare is not None: + output = match_prepare(output) with open(self.get_gold_filename(gold_name), "w") as f: f.write(output) else: diff --git a/src/github.com/smira/aptly/system/run.py b/src/github.com/smira/aptly/system/run.py index e11feea2..8f8819c9 100755 --- a/src/github.com/smira/aptly/system/run.py +++ b/src/github.com/smira/aptly/system/run.py @@ -8,6 +8,7 @@ import fnmatch import sys from lib import BaseTest +from s3_lib import S3Test try: from termcolor import colored @@ -32,7 +33,8 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non for name in dir(testModule): o = getattr(testModule, name) - if not (inspect.isclass(o) and issubclass(o, BaseTest) and o is not BaseTest): + if not (inspect.isclass(o) and issubclass(o, BaseTest) and o is not BaseTest and + o is not S3Test): continue if filters: diff --git a/src/github.com/smira/aptly/system/s3_lib.py b/src/github.com/smira/aptly/system/s3_lib.py new file mode 100644 index 00000000..82f11be4 --- /dev/null +++ b/src/github.com/smira/aptly/system/s3_lib.py @@ -0,0 +1,78 @@ +from lib import BaseTest +import uuid +import os + +try: + import boto + + if 'AWS_SECRET_ACCESS_KEY' in os.environ and 'AWS_ACCESS_KEY_ID' in os.environ: + s3_conn = boto.connect_s3() + else: + s3_conn = None +except ImportError: + s3_conn = None + + +class S3Test(BaseTest): + """ + BaseTest + support for S3 + """ + + def fixture_available(self): + return super(S3Test, self).fixture_available() and s3_conn is not None + + def prepare(self): + self.bucket_name = "aptly-sys-test-" + str(uuid.uuid4()) + self.bucket = s3_conn.create_bucket(self.bucket_name) + self.configOverride["S3PublishEndpoints"] = { + "test1": { + "region": "us-east-1", + "bucket": self.bucket_name, + } + } + + super(S3Test, self).prepare() + + def shutdown(self): + if hasattr(self, "bucket_name"): + if hasattr(self, "bucket"): + keys = self.bucket.list() + if keys: + self.bucket.delete_keys(keys) + s3_conn.delete_bucket(self.bucket_name) + + super(S3Test, self).shutdown() + + def check_path(self, path): + if not hasattr(self, "bucket_contents"): + self.bucket_contents = [key.name for key in self.bucket.list()] + + if path.startswith("public/"): + path = path[7:] + + if path in self.bucket_contents: + return True + + if not path.endswith("/"): + path = path + "/" + + for item in self.bucket_contents: + if item.startswith(path): + return True + + return False + + def check_exists(self, path): + if not self.check_path(path): + raise Exception("path %s doesn't exist" % (path, )) + + def check_not_exists(self, path): + if self.check_path(path): + raise Exception("path %s exists" % (path, )) + + def read_file(self, path): + if path.startswith("public/"): + path = path[7:] + + key = self.bucket.get_key(path) + return key.get_contents_as_string() diff --git a/src/github.com/smira/aptly/system/t01_version/VersionTest_gold b/src/github.com/smira/aptly/system/t01_version/VersionTest_gold index 2593b4ad..b2155618 100644 --- a/src/github.com/smira/aptly/system/t01_version/VersionTest_gold +++ b/src/github.com/smira/aptly/system/t01_version/VersionTest_gold @@ -1 +1 @@ -aptly version: 0.7.1 +aptly version: 0.8 diff --git a/src/github.com/smira/aptly/system/t03_help/MainHelpTest_gold b/src/github.com/smira/aptly/system/t03_help/MainHelpTest_gold index 72fa545e..0c42fb04 100644 --- a/src/github.com/smira/aptly/system/t03_help/MainHelpTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MainHelpTest_gold @@ -4,9 +4,9 @@ upgrade individual packages, take snapshots and publish them back as Debian repositories. aptly's goal is to establish repeatability and controlled changes -in a package-centric environment. aptly allows to fix a set of packages +in a package-centric environment. aptly allows one to fix a set of packages in a repository, so that package installation and upgrade becomes -deterministic. At the same time aptly allows to perform controlled, +deterministic. At the same time aptly allows one to perform controlled, fine-grained changes in repository contents to transition your package environment to new version. diff --git a/src/github.com/smira/aptly/system/t03_help/MainTest_gold b/src/github.com/smira/aptly/system/t03_help/MainTest_gold index 96676be4..b468f99b 100644 --- a/src/github.com/smira/aptly/system/t03_help/MainTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MainTest_gold @@ -5,6 +5,7 @@ Commands: db manage aptly's internal database and package pool graph render graph of relationships mirror manage mirrors of remote repositories + package operations on packages publish manage published repositories repo manage local package repositories serve HTTP serve published repositories diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold index efde9452..9a37486e 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold @@ -1,7 +1,7 @@ Usage: aptly mirror create [ ...] Creates mirror of remote repository, aptly supports both regular and flat Debian repositories exported -via HTTP. aptly would try download Release file from remote repository and verify its' signature. Command +via HTTP and FTP. aptly would try download Release file from remote repository and verify its' signature. Command line format resembles apt utlitily sources.list(5). PPA urls could specified in short format: @@ -24,4 +24,5 @@ Options: -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages + -with-udebs=false: download .udeb packages (Debian installer support) diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold index dcb524ce..d696eb5d 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold @@ -15,4 +15,5 @@ Options: -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages + -with-udebs=false: download .udeb packages (Debian installer support) ERROR: unable to parse command diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorHelpTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorHelpTest_gold index c84e943d..6cefc10e 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorHelpTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorHelpTest_gold @@ -4,9 +4,10 @@ Commands: create create new mirror drop delete mirror - edit edit properties of mirorr + edit edit mirror settings list list mirrors rename renames mirror + search search mirror for packages matching query show show details about mirror update update mirror diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorTest_gold index e0adb074..cf149c99 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorTest_gold @@ -4,9 +4,10 @@ Commands: create create new mirror drop delete mirror - edit edit properties of mirorr + edit edit mirror settings list list mirrors rename renames mirror + search search mirror for packages matching query show show details about mirror update update mirror diff --git a/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold b/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold index 738bf59c..a6df4f0e 100644 --- a/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold @@ -16,4 +16,5 @@ Options: -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages + -with-udebs=false: download .udeb packages (Debian installer support) ERROR: unable to parse flags diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror11Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror11Test_mirror_show index 22eabff4..522449e3 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror11Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror11Test_mirror_show @@ -4,6 +4,7 @@ Distribution: squeeze Components: main, contrib, non-free Architectures: amd64, armel, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show index d9eee928..1a012184 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib, non-free Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror14Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror14Test_mirror_show index 2789c252..8f479d62 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror14Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror14Test_mirror_show @@ -4,6 +4,7 @@ Distribution: ./ Components: Architectures: Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show index 3e6e75d4..816e61d5 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib, non-free Architectures: i386 Download Sources: yes +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror18Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror18Test_mirror_show index abd2a7d1..d2d32be5 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror18Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror18Test_mirror_show @@ -4,6 +4,7 @@ Distribution: maverick Components: main Architectures: amd64, armel, i386, powerpc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror19Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror19Test_mirror_show index 52bf55df..c4425802 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror19Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror19Test_mirror_show @@ -4,17 +4,16 @@ Distribution: wheezy/updates Components: main Architectures: i386 Download Sources: yes +Download .udebs: no Last update: never Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: updates/main updates/contrib updates/non-free -Date: Tue, 11 Mar 2014 21:11:28 UTC Description: Debian 7.0 Security Updates Label: Debian-Security Origin: Debian Suite: stable -Valid-Until: Fri, 21 Mar 2014 21:11:28 UTC Version: 7.0 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show index d5542dde..94f4085f 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib, non-free Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror20Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror20Test_gold index 906b8422..053cfec4 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror20Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror20Test_gold @@ -1,3 +1,3 @@ Downloading http://security.debian.org/dists/wheezy/updates/InRelease... Downloading http://security.debian.org/dists/wheezy/updates/Release... -ERROR: unable to fetch mirror: Get http://security.debian.org/dists/wheezy/updates/Release: http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused +ERROR: unable to fetch mirror: http://security.debian.org/dists/wheezy/updates/Release: Get http://security.debian.org/dists/wheezy/updates/Release: http: error connecting to proxy http://127.0.0.1:3137: dial tcp 127.0.0.1:3137: connection refused diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show index adfb7ff9..385432d3 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show @@ -4,10 +4,11 @@ Distribution: ./binary/ Components: Architectures: Download Sources: no +Download .udebs: no Last update: never Information from release file: Architectures: all -Date: Wed, 30 Jul 2014 15:23:01 UTC +Date: Wed, 01 Oct 2014 18:30:34 UTC Origin: jenkins-ci.org Suite: binary diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror22Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror22Test_mirror_show index add4d654..8b9e103d 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror22Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror22Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy/updates Components: main Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Filter: nginx | Priority (required) Filter With Deps: no Last update: never @@ -12,11 +13,9 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: updates/main updates/contrib updates/non-free -Date: Sun, 13 Jul 2014 12:12:08 UTC Description: Debian 7.0 Security Updates Label: Debian-Security Origin: Debian Suite: stable -Valid-Until: Wed, 23 Jul 2014 12:12:08 UTC Version: 7.0 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror24Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror24Test_gold new file mode 100644 index 00000000..7fc9d2b4 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror24Test_gold @@ -0,0 +1,6 @@ +Downloading http://security.debian.org/dists/wheezy/updates/InRelease... +gpgv: RSA key ID 46925553 +gpgv: Good signature from "Debian Archive Automatic Signing Key (7.0/wheezy) " + +Mirror [mirror24]: http://security.debian.org/ wheezy/updates successfully added. +You can run 'aptly mirror update mirror24' to download repository contents. diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_gold new file mode 100644 index 00000000..6eab7a88 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_gold @@ -0,0 +1,4 @@ +Downloading http://mirror.yandex.ru/debian/dists/wheezy/Release... + +Mirror [mirror25]: http://mirror.yandex.ru/debian/ wheezy [udeb] successfully added. +You can run 'aptly mirror update mirror25' to download repository contents. diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show new file mode 100644 index 00000000..7f5a17eb --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show @@ -0,0 +1,20 @@ +Name: mirror25 +Archive Root URL: http://mirror.yandex.ru/debian/ +Distribution: wheezy +Components: main, contrib, non-free +Architectures: i386 +Download Sources: no +Download .udebs: yes +Last update: never + +Information from release file: +Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc +Codename: wheezy +Components: main contrib non-free +Date: Sat, 12 Jul 2014 10:59:25 UTC +Description: Debian 7.6 Released 12 July 2014 + +Label: Debian +Origin: Debian +Suite: stable +Version: 7.6 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror26Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror26Test_gold new file mode 100644 index 00000000..97027669 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror26Test_gold @@ -0,0 +1 @@ +ERROR: unable to create mirror: debian-installer udebs aren't supported for flat repos diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show index 183348a4..60c7d9de 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show index 804abb9e..5586b8b2 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib Architectures: i386, amd64 Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show index c53b3f5c..9f0029af 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib Architectures: i386, amd64 Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror9Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror9Test_mirror_show index e6d63aa5..1243bde1 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror9Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror9Test_mirror_show @@ -4,6 +4,7 @@ Distribution: squeeze-backports Components: main, contrib, non-free Architectures: amd64, armel, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: @@ -11,11 +12,9 @@ Architectures: amd64 armel i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel po ButAutomaticUpgrades: yes Codename: squeeze-backports Components: main contrib non-free -Date: Fri, 07 Feb 2014 02:56:49 UTC Description: Backports for the Squeeze Distribution Label: Debian Backports NotAutomatic: yes Origin: Debian Backports Suite: squeeze-backports -Valid-Until: Fri, 14 Feb 2014 02:56:49 UTC diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_gold b/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_gold index 359dc7ac..7882ef07 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_gold @@ -1 +1 @@ -Mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy successfully updated. +Mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy [src] successfully updated. diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_mirror_show index 0090012c..f934ffc9 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror1Test_mirror_show @@ -3,10 +3,10 @@ Archive Root URL: http://mirror.yandex.ru/debian/ Distribution: wheezy Components: main Architectures: i386, amd64 -Download Sources: no +Download Sources: yes +Download .udebs: no Filter: nginx Filter With Deps: yes -Last update: 2014-06-28 01:23:25 MSK Number of packages: 56121 Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror3Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror3Test_mirror_show index 59b134fc..d3348f0c 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/EditMirror3Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror3Test_mirror_show @@ -4,7 +4,7 @@ Distribution: wheezy Components: main Architectures: i386, amd64 Download Sources: no -Last update: 2014-06-28 01:23:25 MSK +Download .udebs: no Number of packages: 56121 Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror5Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror5Test_mirror_show index 535f67b8..8790d809 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/EditMirror5Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror5Test_mirror_show @@ -4,17 +4,16 @@ Distribution: wheezy/updates Components: main Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: updates/main updates/contrib updates/non-free -Date: Fri, 25 Jul 2014 03:31:55 UTC Description: Debian 7.0 Security Updates Label: Debian-Security Origin: Debian Suite: stable -Valid-Until: Mon, 04 Aug 2014 03:31:55 UTC Version: 7.0 diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_gold b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_gold new file mode 100644 index 00000000..2a8ac06c --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_gold @@ -0,0 +1,2 @@ +Downloading http://mirror.yandex.ru/debian/dists/wheezy/Release... +Mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy successfully updated. diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show new file mode 100644 index 00000000..3f448097 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show @@ -0,0 +1,20 @@ +Name: wheezy-main +Archive Root URL: http://mirror.yandex.ru/debian/ +Distribution: wheezy +Components: main +Architectures: amd64, s390 +Download Sources: no +Download .udebs: no +Number of packages: 56121 + +Information from release file: +Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc +Codename: wheezy +Components: main contrib non-free +Date: Sat, 12 Jul 2014 10:59:25 UTC +Description: Debian 7.6 Released 12 July 2014 + +Label: Debian +Origin: Debian +Suite: stable +Version: 7.6 diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror7Test_gold b/src/github.com/smira/aptly/system/t04_mirror/EditMirror7Test_gold new file mode 100644 index 00000000..04552006 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror7Test_gold @@ -0,0 +1,2 @@ +Downloading http://mirror.yandex.ru/debian/dists/wheezy/Release... +ERROR: unable to edit: architecture x56 not available in repo [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_gold b/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_gold new file mode 100644 index 00000000..1291476b --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_gold @@ -0,0 +1 @@ +Mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy [udeb] successfully updated. diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_mirror_show new file mode 100644 index 00000000..3aa03131 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror8Test_mirror_show @@ -0,0 +1,20 @@ +Name: wheezy-main +Archive Root URL: http://mirror.yandex.ru/debian/ +Distribution: wheezy +Components: main +Architectures: i386, amd64 +Download Sources: no +Download .udebs: yes +Number of packages: 56121 + +Information from release file: +Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc +Codename: wheezy +Components: main contrib non-free +Date: Sat, 26 Apr 2014 09:27:11 UTC +Description: Debian 7.5 Released 26 April 2014 + +Label: Debian +Origin: Debian +Suite: stable +Version: 7.5 diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror9Test_gold b/src/github.com/smira/aptly/system/t04_mirror/EditMirror9Test_gold new file mode 100644 index 00000000..a2e1e5a4 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror9Test_gold @@ -0,0 +1 @@ +ERROR: unable to edit: flat mirrors don't support udebs diff --git a/src/github.com/smira/aptly/system/t04_mirror/SearchMirror1Test_gold b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror1Test_gold new file mode 100644 index 00000000..52405682 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror1Test_gold @@ -0,0 +1,4043 @@ + +aolserver4-dev_4.5.1-15.1_i386 +apache2-prefork-dev_2.2.22-13+deb7u1_i386 +apache2-threaded-dev_2.2.22-13+deb7u1_i386 +apcalc-dev_2.12.4.4-3_i386 +aplus-fsf-dev_4.22.1-6_i386 +aroarfw-dev_0.1~beta4-5_all +asterisk-dev_1:1.8.13.1~dfsg1-3+deb7u3_all +atfs-dev_1.4pl6-11_i386 +audacious-dev_3.2.4-1_i386 +autotools-dev_20120608.1_all +binutils-dev_2.22-8_i386 +biosquid-dev_1.9g+cvs20050121-2_i386 +bitlbee-dev_3.0.5-1.2_all +blacs-pvm-dev_1.1-21_i386 +blends-dev_0.6.16.2_all +blktap-dev_2.0.90-1_i386 +blt-dev_2.4z-4.2_i386 +boinc-dev_7.0.27+dfsg-5_i386 +boolstuff-dev_0.1.12-3_i386 +cairo-dock-dev_3.0.0-2+deb7u1_i386 +cernlib-base-dev_20061220+dfsg3-2_all +cernlib-core-dev_20061220+dfsg3-2_all +chipmunk-dev_5.3.4-1_i386 +cimg-dev_1.4.9-2_all +clearsilver-dev_0.10.5-1.3_i386 +cli-common-dev_0.8.2_all +clinica-dev_0.2.1~dfsg-1_i386 +clisp-dev_1:2.49-8.1_i386 +cluster-glue-dev_1.0.9+hg2665-1_i386 +codeblocks-dev_10.05-2.1_i386 +coinor-libcbc-dev_2.5.0-3_i386 +coinor-libcgl-dev_0.55.0-1.1_i386 +coinor-libclp-dev_1.12.0-2.1_i386 +coinor-libcoinutils-dev_2.6.4-3_i386 +coinor-libdylp-dev_1.6.0-1.1_i386 +coinor-libflopc++-dev_1.0.6-3.1_i386 +coinor-libipopt-dev_3.10.2-1.1_i386 +coinor-libosi-dev_0.103.0-1_i386 +coinor-libsymphony-dev_5.2.4-1.2_i386 +coinor-libvol-dev_1.1.7-1_i386 +collectd-dev_5.1.0-3_all +comerr-dev_2.1-1.42.5-1.1_i386 +condor-dev_7.8.2~dfsg.1-1+deb7u1_i386 +config-package-dev_4.13_all +connman-dev_1.0-1.1+wheezy1+b1_i386 +console-tools-dev_1:0.2.3dbs-70_i386 +coop-computing-tools-dev_3.5.1-2_i386 +corosync-dev_1.4.2-3_i386 +courier-authlib-dev_0.63.0-6+b1_i386 +crtmpserver-dev_1.0~dfsg-3_i386 +ctapi-dev_1.1_all +ctn-dev_3.0.6-13+b2_i386 +cyrus-dev_2.4.16-4+deb7u1_all +dcap-dev_2.47.6-2_i386 +dico-dev_2.1-3+b2_i386 +dictionaries-common-dev_1.12.11_all +dietlibc-dev_0.33~cvs20120325-4_i386 +dolfin-dev_1.0.0-7_all +dovecot-dev_1:2.1.7-7_i386 +dpkg-dev_1.16.12_all +drac-dev_1.12-7.2_i386 +drizzle-plugin-dev_1:7.1.36-stable-1_i386 +dssi-dev_1.1.1~dfsg0-1_all +e2fslibs-dev_1.42.5-1.1_i386 +emerillon-dev_0.1.90-1_i386 +eog-dev_3.4.2-1+build1_all +epiphany-browser-dev_3.4.2-2.1_i386 +erlang-dev_1:15.b.1-dfsg-4+deb7u1_i386 +erlang-esdl-dev_1.2-2_all +etl-dev_0.04.15-1_i386 +evolution-data-server-dev_3.4.4-3_i386 +evolution-dev_3.4.4-3_i386 +exim4-dev_4.80-7_i386 +expect-dev_5.45-2_i386 +expeyes-firmware-dev_2.0.0-3_all +extremetuxracer-gimp-dev_0.4-5_all +falconpl-dev_0.9.6.9-git20120606-2_i386 +fatrat-dev_1.1.3-5_all +fcitx-libs-dev_1:4.2.4.1-7_i386 +fenix-dev_0.92a.dfsg1-9_all +festival-dev_1:2.1~release-5.1_i386 +fftw-dev_2.1.5-1_i386 +finch-dev_2.10.9-1~deb7u1_all +firebird-dev_2.5.2.26540.ds4-1~deb7u1_i386 +flite1-dev_1.4-release-6_i386 +flow-tools-dev_1:0.68-12.1+b1_i386 +fosfat-dev_0.4.0-3_i386 +freeglut3-dev_2.6.0-4_i386 +freetds-dev_0.91-2+deb7u1_i386 +frei0r-plugins-dev_1.1.22git20091109-1.2_i386 +ftgl-dev_2.1.3~rc5-4_all +ftplib-dev_3.1-1-9_i386 +gambas3-dev_3.1.1-2+b1_i386 +gap-dev_4r4p12-2_i386 +gauche-dev_0.9.1-5.1_i386 +gcc-4.6-plugin-dev_4.6.3-14_i386 +gcc-4.7-plugin-dev_4.7.2-5_i386 +gcin-dev_2.7.6.1+dfsg-1_all +gedit-dev_3.4.2-1_all +gem-dev_1:0.93.3-5_all +genius-dev_1.0.14-1_i386 +gfxboot-dev_4.5.0-3_i386 +giblib-dev_1.2.4-8_i386 +glabels-dev_3.0.0-3+b1_i386 +glee-dev_5.4.0-1_i386 +gmpc-dev_11.8.16-6_i386 +gnash-dev_0.8.11~git20120629-1+deb7u1_i386 +gnome-control-center-dev_1:3.4.3.1-2_all +gnome-settings-daemon-dev_3.4.2+git20121218.7c1322-3+deb7u3_i386 +gnome-video-effects-dev_0.4.0-1_all +gnumach-dev_2:1.3.99.dfsg.git20120610-1_i386 +gnunet-dev_0.9.3-7_i386 +gnunet-gtk-dev_0.9.3-1_i386 +gnuradio-dev_3.5.3.2-1_i386 +gosa-dev_2.7.4-4.3~deb7u1_all +gpe-ownerinfo-dev_0.28-3_i386 +gpsim-dev_0.26.1-2.1_i386 +graphviz-dev_2.26.3-14+deb7u1_all +grass-dev_6.4.2-2_i386 +gridengine-drmaa-dev_6.2u5-7.1_i386 +gromacs-dev_4.5.5-2_i386 +gsettings-desktop-schemas-dev_3.4.2-3_i386 +gthumb-dev_3:3.0.1-2_i386 +guile-1.6-dev_1.6.8-10.3_i386 +guile-1.8-dev_1.8.8+1-8_i386 +guile-2.0-dev_2.0.5+1-3_i386 +guile-cairo-dev_1.4.0-3_i386 +heartbeat-dev_1:3.0.5-3_i386 +heimdal-dev_1.6~git20120403+dfsg1-2_i386 +hime-dev_0.9.9+git20120619+dfsg-1_all +hybrid-dev_1:7.2.2.dfsg.2-10_all +icedove-dev_10.0.12-1_i386 +inn2-dev_2.5.3-3_i386 +inventor-dev_2.1.5-10-16_i386 +iproute-dev_20120521-3+b3_i386 +iptables-dev_1.4.14-3.1_i386 +irssi-dev_0.8.15-5_i386 +isc-dhcp-dev_4.2.2.dfsg.1-5+deb70u6_i386 +itcl3-dev_3.4.1-1_i386 +itk3-dev_3.3-4_i386 +ivtools-dev_1.2.10a1-1_i386 +kadu-dev_0.11.2-1_all +kannel-dev_1.4.3-2+b2_i386 +kde-workspace-dev_4:4.8.4-6_i386 +kdebase-workspace-dev_4:4.8.4-6_all +kdelibs5-dev_4:4.8.4-4_i386 +kdemultimedia-dev_4:4.8.4-2_i386 +kdepimlibs5-dev_4:4.8.4-2_i386 +kdevelop-dev_4:4.3.1-3+b1_i386 +kdevplatform-dev_1.3.1-2_i386 +kmymoney-dev_4.6.2-3.2_i386 +konwert-dev_1.8-11.2_all +lam4-dev_7.1.4-3_i386 +lesstif2-dev_1:0.95.2-1.1_i386 +lib3ds-dev_1.3.0-6_i386 +lib4store-dev_1.1.4-2_i386 +lib64bz2-dev_1.0.6-4_i386 +lib64expat1-dev_2.1.0-1+deb7u1_i386 +lib64ffi-dev_3.0.10-3_i386 +lib64ncurses5-dev_5.9-10_i386 +lib64readline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +lib64readline6-dev_6.2+dfsg-0.1_i386 +lib64z1-dev_1:1.2.7.dfsg-13_i386 +liba52-0.7.4-dev_0.7.4-16_i386 +libaa1-dev_1.4p5-40_i386 +libaac-tactics-ocaml-dev_0.2.pl2-7_i386 +libaacs-dev_0.4.0-1_i386 +libaal-dev_1.0.5-5.1_i386 +libabiword-2.9-dev_2.9.2+svn20120603-8_i386 +libaccountsservice-dev_0.6.21-8_i386 +libace-dev_6.0.3+dfsg-0.1_i386 +libace-flreactor-dev_6.0.3+dfsg-0.1_i386 +libace-foxreactor-dev_6.0.3+dfsg-0.1_i386 +libace-htbp-dev_6.0.3+dfsg-0.1_i386 +libace-inet-dev_6.0.3+dfsg-0.1_i386 +libace-inet-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-qtreactor-dev_6.0.3+dfsg-0.1_i386 +libace-rmcast-dev_6.0.3+dfsg-0.1_i386 +libace-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-tkreactor-dev_6.0.3+dfsg-0.1_i386 +libace-tmcast-dev_6.0.3+dfsg-0.1_i386 +libace-xtreactor-dev_6.0.3+dfsg-0.1_i386 +libacexml-dev_6.0.3+dfsg-0.1_i386 +libacl1-dev_2.2.51-8_i386 +libacpi-dev_0.2-4_i386 +libacr38ucontrol-dev_1.7.11-1_i386 +libadasockets4-dev_1.8.10-2_i386 +libaddresses-dev_0.4.7-1+b5_i386 +libaddressview-dev_0.4.7-1+b5_i386 +libadios-dev_1.3-11_i386 +libadminutil-dev_1.1.15-1_i386 +libadns1-dev_1.4-2_i386 +libadolc-dev_2.3.0-1_i386 +libadplug-dev_2.2.1+dfsg3-0.1_i386 +libafflib-dev_3.6.6-1.1+b1_i386 +libafrodite-0.12-dev_0.12.1-3_i386 +libafterimage-dev_2.2.11-7_i386 +libagg-dev_2.5+dfsg1-8_i386 +libagrep-ocaml-dev_1.0-11+b3_i386 +libahven3-dev_2.1-4_i386 +libaiksaurus-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaiksaurusgtk-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaio-dev_0.3.109-3_i386 +libakonadi-dev_1.7.2-3_i386 +libalberta2-dev_2.0.1-5_i386 +libaldmb1-dev_1:0.9.3-5.4_i386 +libalglib-dev_2.6.0-6_i386 +libalkimia-dev_4.3.2-1.1_i386 +liballeggl4-dev_2:4.4.2-2.1_i386 +liballegro4.2-dev_2:4.4.2-2.1_i386 +libalog0.4.1-base-dev_0.4.1-2_i386 +libalog0.4.1-full-dev_0.4.1-2_i386 +libalsa-ocaml-dev_0.2.1-1+b1_i386 +libalsaplayer-dev_0.99.80-5.1_i386 +libalure-dev_1.2-6_i386 +libalut-dev_1.1.0-3_i386 +libampsharp-cil-dev_2.0.4-2_all +libamu-dev_6.2+rc20110530-3_i386 +libanalitza-dev_4:4.8.4-2_i386 +libanet0.1-dev_0.1-3_i386 +libanjuta-dev_2:3.4.3-1_i386 +libann-dev_1.1.2+doc-3_i386 +libanthy-dev_9100h-16_i386 +libantlr-dev_2.7.7+dfsg-4_i386 +libantlr3c-dev_3.2-2_i386 +libao-dev_1.1.0-2_i386 +libao-ocaml-dev_0.2.0-1+b2_i386 +libaosd-dev_0.2.7-1_i386 +libapache2-mod-perl2-dev_2.0.7-3_all +libapertium3-3.1-0-dev_3.1.0-2_i386 +libapm-dev_3.2.2-14_i386 +libapol-dev_3.3.7-3_i386 +libapparmor-dev_2.7.103-4_i386 +libappindicator-dev_0.4.92-2_i386 +libappindicator0.1-cil-dev_0.4.92-2_all +libappindicator3-dev_0.4.92-2_i386 +libapq-postgresql3.2.0-dev_3.2.0-2_i386 +libapq3.2.0-dev_3.2.0-1_i386 +libapr-memcache-dev_0.7.0-1_i386 +libapr1-dev_1.4.6-3+deb7u1_i386 +libapreq2-dev_2.13-1+b2_i386 +libapron-dev_0.9.10-5.2_all +libapron-ocaml-dev_0.9.10-5.2+b3_i386 +libaprutil1-dev_1.4.1-3_i386 +libapt-pkg-dev_0.9.7.9+deb7u1_i386 +libaqbanking34-dev_5.0.24-3_i386 +libaqsis-dev_1.8.1-3_i386 +libarchive-dev_3.0.4-3+nmu1_i386 +libargtable2-dev_12-1_i386 +libarmadillo-dev_1:3.2.3+dfsg-1_i386 +libarpack++2-dev_2.3-2_i386 +libarpack2-dev_3.1.1-2.1_i386 +libart-2.0-dev_2.3.21-2_i386 +libart2.0-cil-dev_2.24.2-3_all +libasio-dev_1.4.1-3.2_all +libasis2010-dev_2010-5_i386 +libasm-dev_0.152-1+wheezy1_i386 +libasound2-dev_1.0.25-4_i386 +libaspell-dev_0.60.7~20110707-1_i386 +libass-dev_0.10.0-3_i386 +libassa3.5-5-dev_3.5.1-2_i386 +libassimp-dev_3.0~dfsg-1_i386 +libassuan-dev_2.0.3-1_i386 +libast2-dev_0.7-6+b1_i386 +libasyncns-dev_0.8-4_i386 +libatasmart-dev_0.19-1_i386 +libatd-ocaml-dev_1.0.1-1+b1_i386 +libatdgen-ocaml-dev_1.2.2-1+b1_i386 +libatk-bridge2.0-dev_2.5.3-2_i386 +libatk1.0-dev_2.4.0-2_i386 +libatkmm-1.6-dev_2.22.6-1_i386 +libatlas-base-dev_3.8.4-9+deb7u1_i386 +libatlas-cpp-0.6-dev_0.6.2-3_i386 +libatlas-dev_3.8.4-9+deb7u1_all +libatm1-dev_1:2.5.1-1.5_i386 +libatomic-ops-dev_7.2~alpha5+cvs20101124-1+deb7u1_i386 +libatomicparsley-dev_2.1.2-1_i386 +libatspi-dev_1.32.0-2_i386 +libatspi2.0-dev_2.5.3-2_i386 +libattica-dev_0.2.0-1_i386 +libattr1-dev_1:2.4.46-8_i386 +libaubio-dev_0.3.2-4.2+b1_i386 +libaudio-dev_1.9.3-5wheezy1_i386 +libaudiofile-dev_0.3.4-2_i386 +libaudiomask-dev_1.0-2_i386 +libaudit-dev_1:1.7.18-1.1_i386 +libaugeas-dev_0.10.0-1_i386 +libaunit2-dev_1.03-7_i386 +libautotrace-dev_0.31.1-16+b1_i386 +libautounit-dev_0.20.1-4_i386 +libavahi-cil-dev_0.6.19-4.2_all +libavahi-client-dev_0.6.31-2_i386 +libavahi-common-dev_0.6.31-2_i386 +libavahi-compat-libdnssd-dev_0.6.31-2_i386 +libavahi-core-dev_0.6.31-2_i386 +libavahi-glib-dev_0.6.31-2_i386 +libavahi-gobject-dev_0.6.31-2_i386 +libavahi-qt4-dev_0.6.31-2_i386 +libavahi-ui-cil-dev_0.6.19-4.2_all +libavahi-ui-dev_0.6.31-2_i386 +libavahi-ui-gtk3-dev_0.6.31-2_i386 +libavbin-dev_7-1.3_i386 +libavc1394-dev_0.5.4-2_i386 +libavcodec-dev_6:0.8.10-1_i386 +libavdevice-dev_6:0.8.10-1_i386 +libavfilter-dev_6:0.8.10-1_i386 +libavformat-dev_6:0.8.10-1_i386 +libavifile-0.7-dev_1:0.7.48~20090503.ds-13_i386 +libavl-dev_0.3.5-3_i386 +libavogadro-dev_1.0.3-5_i386 +libavutil-dev_6:0.8.10-1_i386 +libaws2.10.2-dev_2.10.2-4_i386 +libax25-dev_0.0.12-rc2+cvs20120204-2_i386 +libbabl-dev_0.1.10-1_i386 +libball1.4-dev_1.4.1+20111206-4_i386 +libballview1.4-dev_1.4.1+20111206-4_i386 +libbam-dev_0.1.18-1_i386 +libbamf-dev_0.2.118-1_i386 +libbamf3-dev_0.2.118-1_i386 +libbarry-dev_0.18.3-5_i386 +libbatteries-ocaml-dev_1.4.3-1_i386 +libbdd-dev_2.4-8_i386 +libbeecrypt-dev_4.2.1-4_i386 +libbenchmark-ocaml-dev_0.9-2+b3_i386 +libbfb0-dev_0.23-1.1_i386 +libbg1-dev_1.106-1_i386 +libbibutils-dev_4.12-5_i386 +libbin-prot-camlp4-dev_2.0.7-1_i386 +libbind-dev_1:9.8.4.dfsg.P1-6+nmu2+deb7u1_i386 +libbind4-dev_6.0-1_i386 +libbinio-dev_1.4+dfsg1-1_i386 +libbiniou-ocaml-dev_1.0.0-1+b1_i386 +libbio2jack0-dev_0.9-2.1_i386 +libbiococoa-dev_2.2.2-1+b2_i386 +libbiosig-dev_1.3.0-2_i386 +libbisho-common-dev_0.27.2+git20111122.9e68ef3d-1_i386 +libbison-dev_1:2.5.dfsg-2.1_i386 +libbitmask-dev_2.0-2_i386 +libbitstream-dev_1.0-1_all +libbitstring-ocaml-dev_2.0.2-3+b1_i386 +libbjack-ocaml-dev_0.1.3-5+b1_i386 +libblacs-mpi-dev_1.1-31_i386 +libblas-dev_1.2.20110419-5_i386 +libbliss-dev_0.72-4_i386 +libblitz0-dev_1:0.9-13_i386 +libblkid-dev_2.20.1-5.3_i386 +libblocksruntime-dev_0.1-1_i386 +libbluedevil-dev_1.9.2-1_i386 +libbluetooth-dev_4.99-2_i386 +libbluray-dev_1:0.2.2-1_i386 +libbml-dev_0.6.1-1_i386 +libbobcat-dev_3.01.00-1+b1_i386 +libbogl-dev_0.1.18-8+b1_i386 +libbognor-regis-dev_0.6.12+git20101007.02c25268-7_i386 +libbonobo2-dev_2.24.3-1_i386 +libbonoboui2-dev_2.24.3-1_i386 +libboo-cil-dev_0.9.5~git20110729.r1.202a430-2_all +libboost-all-dev_1.49.0.1_i386 +libboost-chrono-dev_1.49.0.1_i386 +libboost-chrono1.49-dev_1.49.0-3.2_i386 +libboost-date-time-dev_1.49.0.1_i386 +libboost-date-time1.49-dev_1.49.0-3.2_i386 +libboost-dev_1.49.0.1_i386 +libboost-filesystem-dev_1.49.0.1_i386 +libboost-filesystem1.49-dev_1.49.0-3.2_i386 +libboost-graph-dev_1.49.0.1_i386 +libboost-graph-parallel-dev_1.49.0.1_i386 +libboost-graph-parallel1.49-dev_1.49.0-3.2_i386 +libboost-graph1.49-dev_1.49.0-3.2_i386 +libboost-iostreams-dev_1.49.0.1_i386 +libboost-iostreams1.49-dev_1.49.0-3.2_i386 +libboost-locale-dev_1.49.0.1_i386 +libboost-locale1.49-dev_1.49.0-3.2_i386 +libboost-math-dev_1.49.0.1_i386 +libboost-math1.49-dev_1.49.0-3.2_i386 +libboost-mpi-dev_1.49.0.1_i386 +libboost-mpi-python-dev_1.49.0.1_i386 +libboost-mpi-python1.49-dev_1.49.0-3.2_i386 +libboost-mpi1.49-dev_1.49.0-3.2_i386 +libboost-program-options-dev_1.49.0.1_i386 +libboost-program-options1.49-dev_1.49.0-3.2_i386 +libboost-python-dev_1.49.0.1_i386 +libboost-python1.49-dev_1.49.0-3.2_i386 +libboost-random-dev_1.49.0.1_i386 +libboost-random1.49-dev_1.49.0-3.2_i386 +libboost-regex-dev_1.49.0.1_i386 +libboost-regex1.49-dev_1.49.0-3.2_i386 +libboost-serialization-dev_1.49.0.1_i386 +libboost-serialization1.49-dev_1.49.0-3.2_i386 +libboost-signals-dev_1.49.0.1_i386 +libboost-signals1.49-dev_1.49.0-3.2_i386 +libboost-system-dev_1.49.0.1_i386 +libboost-system1.49-dev_1.49.0-3.2_i386 +libboost-test-dev_1.49.0.1_i386 +libboost-test1.49-dev_1.49.0-3.2_i386 +libboost-thread-dev_1.49.0.1_i386 +libboost-thread1.49-dev_1.49.0-3.2_i386 +libboost-timer-dev_1.49.0.1_i386 +libboost-timer1.49-dev_1.49.0-3.2_i386 +libboost-wave-dev_1.49.0.1_i386 +libboost-wave1.49-dev_1.49.0-3.2_i386 +libboost1.49-all-dev_1.49.0-3.2_i386 +libboost1.49-dev_1.49.0-3.2_i386 +libbotan1.10-dev_1.10.5-1_i386 +libbox-dev_2.5-2_i386 +libbox2d-dev_2.0.1+dfsg1-1_i386 +libbpp-core-dev_2.0.3-1_i386 +libbpp-phyl-dev_2.0.3-1_i386 +libbpp-popgen-dev_2.0.3-1_i386 +libbpp-qt-dev_2.0.2-1_i386 +libbpp-raa-dev_2.0.3-1_i386 +libbpp-seq-dev_2.0.3-1_i386 +libbrahe-dev_1.3.2-3_i386 +libbrasero-media3-dev_3.4.1-4_i386 +libbrlapi-dev_4.4-10+deb7u1_i386 +libbs2b-dev_3.1.0+dfsg-2_i386 +libbsd-dev_0.4.2-1_i386 +libbse-dev_0.7.4-5_all +libbt-dev_0.70.1-13_i386 +libbtparse-dev_0.63-1_i386 +libbuffy-dev_1.7-1_i386 +libbulletml-dev_0.0.6-5_i386 +libburn-dev_1.2.2-2_i386 +libbuzztard-dev_0.5.0-4_i386 +libbz2-dev_1.0.6-4_i386 +libbz2-ocaml-dev_0.6.0-6+b2_i386 +libc-ares-dev_1.9.1-3_i386 +libc-client2007e-dev_8:2007f~dfsg-2_i386 +libc6-dev_2.13-38+deb7u1_i386 +libcableswig-dev_0.1.0+cvs20111009-1_i386 +libcaca-dev_0.99.beta18-1_i386 +libcairo-ocaml-dev_1:1.2.0-2+b1_i386 +libcairo2-dev_1.12.2-3_i386 +libcairomm-1.0-dev_1.10.0-1_i386 +libcal3d12-dev_0.11.0-4.1_i386 +libcalendar-ocaml-dev_2.03-1+b2_i386 +libcamel1.2-dev_3.4.4-3_i386 +libcameleon-ocaml-dev_1.9.21-2+b1_i386 +libcaml2html-ocaml-dev_1.4.1-3_i386 +libcamlimages-ocaml-dev_1:4.0.1-4+b2_i386 +libcamljava-ocaml-dev_0.3-1+b3_i386 +libcamltemplate-ocaml-dev_1.0.2-1+b2_i386 +libcamomile-ocaml-dev_0.8.4-2_i386 +libcanberra-dev_0.28-6_i386 +libcanberra-gtk-common-dev_0.28-6_all +libcanberra-gtk-dev_0.28-6_i386 +libcanberra-gtk3-dev_0.28-6_i386 +libcanlock2-dev_2b-6_i386 +libcanna1g-dev_3.7p3-11_i386 +libcap-dev_1:2.22-1.2_i386 +libcap-ng-dev_0.6.6-2_i386 +libcapi20-dev_1:3.25+dfsg1-3.3~deb7u1_i386 +libcapsinetwork-dev_0.3.0-7_i386 +libcaribou-dev_0.4.4-1_i386 +libcbf-dev_0.7.9.1-3_i386 +libccaudio2-dev_2.0.5-3_i386 +libccfits-dev_2.4-1_i386 +libcconv-dev_0.6.2-1_i386 +libccrtp-dev_2.0.3-4_i386 +libccs-dev_3.0.12-3.2+deb7u2_i386 +libccscript3-dev_1.1.7-2_i386 +libccss-dev_0.5.0-4_i386 +libcdaudio-dev_0.99.12p2-12_i386 +libcdb-dev_0.78_i386 +libcdd-dev_094b.dfsg-4.2_i386 +libcddb2-dev_1.3.2-3_i386 +libcdi-dev_1.5.4+dfsg.1-5_i386 +libcdio-cdda-dev_0.83-4_i386 +libcdio-dev_0.83-4_i386 +libcdio-paranoia-dev_0.83-4_i386 +libcdk5-dev_5.0.20060507-4_i386 +libcdparanoia-dev_3.10.2+debian-10.1_i386 +libcec-dev_1.6.2-1.1_i386 +libcegui-mk2-dev_0.7.6-2+b1_i386 +libcext-dev_6.1.1-2_i386 +libcf-ocaml-dev_0.10-3+b3_i386 +libcfg-dev_1.4.2-3_i386 +libcfitsio3-dev_3.300-2_i386 +libcgal-dev_4.0-5_i386 +libcgic-dev_2.05-3_i386 +libcgicc5-dev_3.2.9-3_i386 +libcgns-dev_3.1.3.4-1+b1_i386 +libcgroup-dev_0.38-1_i386 +libcgsi-gsoap-dev_1.3.5-1_i386 +libchamplain-0.12-dev_0.12.3-1_i386 +libchamplain-gtk-0.12-dev_0.12.3-1_i386 +libcharls-dev_1.0-2_i386 +libchasen-dev_2.4.5-6_i386 +libcheese-dev_3.4.2-2_i386 +libcheese-gtk-dev_3.4.2-2_i386 +libchewing3-dev_0.3.3-4_i386 +libchicken-dev_4.7.0-1_i386 +libchipcard-dev_5.0.3beta-3_i386 +libchise-dev_0.3.0-2+b1_i386 +libchm-dev_2:0.40a-2_i386 +libchromaprint-dev_0.6-2_i386 +libcib1-dev_1.1.7-1_i386 +libcitadel-dev_8.14-1_i386 +libcitygml0-dev_0.14+svn128-1+3p0p1+4_i386 +libck-connector-dev_0.4.5-3.1_i386 +libckyapplet1-dev_1.1.0-12_i386 +libclalsadrv-dev_2.0.0-3_all +libclam-dev_1.4.0-5.1_i386 +libclam-qtmonitors-dev_1.4.0-3.1_i386 +libclamav-dev_0.98.1+dfsg-1+deb7u3_i386 +libclang-common-dev_1:3.0-6.2_i386 +libclang-dev_1:3.0-6.2_i386 +libclanlib-dev_1.0~svn3827-3_i386 +libclassad-dev_7.8.2~dfsg.1-1+deb7u1_i386 +libclaw-application-dev_1.7.0-3_i386 +libclaw-configuration-file-dev_1.7.0-3_i386 +libclaw-dev_1.7.0-3_i386 +libclaw-dynamic-library-dev_1.7.0-3_i386 +libclaw-graphic-dev_1.7.0-3_i386 +libclaw-logger-dev_1.7.0-3_i386 +libclaw-net-dev_1.7.0-3_i386 +libclaw-tween-dev_1.7.0-3_i386 +libclaws-mail-dev_3.8.1-2_i386 +libclhep-dev_2.1.2.3-1_i386 +libcli-dev_1.9.6-1_i386 +libclippoly-dev_0.11-3_i386 +libclips-dev_6.24-3_i386 +libcliquer-dev_1.21-1_i386 +libcln-dev_1.3.2-1.2_i386 +libcloog-isl-dev_0.17.0-3_i386 +libcloog-ppl-dev_0.15.11-4_i386 +libclthreads-dev_2.4.0-4_i386 +libclucene-dev_0.9.21b-2+b1_i386 +libclustalo-dev_1.1.0-1_i386 +libcluster-glue-dev_1.0.9+hg2665-1_all +libclutter-1.0-dev_1.10.8-2_i386 +libclutter-cil-dev_1.0.0~alpha3~git20090817.r1.349dba6-8_all +libclutter-gst-dev_1.5.4-1+build0_i386 +libclutter-gtk-1.0-dev_1.2.0-2_i386 +libclutter-imcontext-0.1-dev_0.1.4-3_i386 +libcluttergesture-dev_0.0.2.1-7_i386 +libclxclient-dev_3.6.1-6_i386 +libcman-dev_3.0.12-3.2+deb7u2_i386 +libcminpack-dev_1.2.2-1_i386 +libcmis-dev_0.1.0-1+b1_i386 +libcmor-dev_2.8.0-2+b1_i386 +libcmph-dev_0.9-1_i386 +libcneartree-dev_3.1.1-1_i386 +libcnf-dev_4.0-2_i386 +libcob1-dev_1.1-1_i386 +libcogl-dev_1.10.2-7_i386 +libcogl-pango-dev_1.10.2-7_i386 +libcoin60-dev_3.1.3-2.2_i386 +libcojets2-dev_20061220+dfsg3-2_i386 +libcollectdclient-dev_5.1.0-3_i386 +libcollection-dev_0.1.3-2_i386 +libcolorblind-dev_0.0.1-1_i386 +libcolord-dev_0.1.21-1_i386 +libcolord-gtk-dev_0.1.21-1_i386 +libcolorhug-dev_0.1.10-1_i386 +libcomedi-dev_0.10.0-3_i386 +libcommoncpp2-dev_1.8.1-5_i386 +libcompfaceg1-dev_1:1.5.2-5_i386 +libconcord-dev_0.24-1.1_i386 +libconfdb-dev_1.4.2-3_i386 +libconfig++-dev_1.4.8-5_i386 +libconfig++8-dev_1.4.8-5_i386 +libconfig-dev_1.4.8-5_i386 +libconfig-file-ocaml-dev_1.1-1_i386 +libconfig8-dev_1.4.8-5_i386 +libconfuse-dev_2.7-4_i386 +libcontactsdb-dev_0.5-8_i386 +libcoq-ocaml-dev_8.3.pl4+dfsg-2_i386 +libcore-ocaml-dev_107.01-5_i386 +libcorelinux-dev_0.4.32-7.3_i386 +libcoroipcc-dev_1.4.2-3_i386 +libcoroipcs-dev_1.4.2-3_i386 +libcorosync-dev_1.4.2-3_all +libcos4-dev_4.1.6-2_i386 +libcothreads-ocaml-dev_0.10-3+b3_i386 +libcoyotl-dev_3.1.0-5_i386 +libcpg-dev_1.4.2-3_i386 +libcpl-dev_6.1.1-2_i386 +libcppcutter-dev_1.1.7-1.2_i386 +libcppunit-dev_1.12.1-4_i386 +libcppunit-subunit-dev_0.0.8+bzr176-1_i386 +libcpputest-dev_3.1-2_i386 +libcpufreq-dev_008-1_i386 +libcpuset-dev_1.0-3_i386 +libcqrlib2-dev_1.1.2-1_i386 +libcr-dev_0.8.5-2_i386 +libcrack2-dev_2.8.19-3_i386 +libcreal-ocaml-dev_0.7-6+b3_i386 +libcrmcluster1-dev_1.1.7-1_i386 +libcrmcommon2-dev_1.1.7-1_i386 +libcroco3-dev_0.6.6-2_i386 +libcry-ocaml-dev_0.2.2-1+b1_i386 +libcryptgps-ocaml-dev_0.2.1-7+b3_i386 +libcrypto++-dev_5.6.1-6_i386 +libcryptokit-ocaml-dev_1.5-1_i386 +libcryptsetup-dev_2:1.4.3-4_i386 +libcryptui-dev_3.2.2-1_i386 +libcrystalhd-dev_1:0.0~git20110715.fdd2f19-9_i386 +libcsfml-dev_1.6-1_i386 +libcsnd-dev_1:5.17.11~dfsg-3_all +libcsoap-dev_1.1.0-17.1_i386 +libcsound64-dev_1:5.17.11~dfsg-3_all +libcsoundac-dev_1:5.17.11~dfsg-3_all +libcsv-ocaml-dev_1.2.2-1+b1_i386 +libctapimkt0-dev_1.0.1-1.1_i386 +libctdb-dev_1.12+git20120201-4_i386 +libctemplate-dev_2.2-3_i386 +libctl-dev_3.1.0-5_i386 +libctpl-dev_0.3.3.dfsg-2_i386 +libcuba3-dev_3.0+20111124-2_i386 +libcudf-dev_0.6.2-1_i386 +libcudf-ocaml-dev_0.6.2-1_i386 +libcue-dev_1.4.0-1_i386 +libcunit1-dev_2.1-0.dfsg-10_i386 +libcunit1-ncurses-dev_2.1-0.dfsg-10_i386 +libcups2-dev_1.5.3-5+deb7u1_i386 +libcupscgi1-dev_1.5.3-5+deb7u1_i386 +libcupsdriver1-dev_1.5.3-5+deb7u1_i386 +libcupsfilters-dev_1.0.18-2.1+deb7u1_i386 +libcupsimage2-dev_1.5.3-5+deb7u1_i386 +libcupsmime1-dev_1.5.3-5+deb7u1_i386 +libcupsppdc1-dev_1.5.3-5+deb7u1_i386 +libcupt2-dev_2.5.9_i386 +libcurl-ocaml-dev_0.5.3-2+b1_i386 +libcurl4-gnutls-dev_7.26.0-1+wheezy9_i386 +libcurl4-nss-dev_7.26.0-1+wheezy9_i386 +libcurl4-openssl-dev_7.26.0-1+wheezy9_i386 +libcurses-ocaml-dev_1.0.3-2_i386 +libcutter-dev_1.1.7-1.2_i386 +libcv-dev_2.3.1-11_i386 +libcvaux-dev_2.3.1-11_i386 +libcvc3-dev_2.4.1-4_i386 +libcvector2-dev_1.0.3-1_i386 +libcvm1-dev_0.96-1+b1_i386 +libcw3-dev_3.0.2-1_i386 +libcwidget-dev_0.5.16-3.4_i386 +libcwiid-dev_0.6.00+svn201-3+b1_i386 +libcwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libcxgb3-dev_1.3.1-1_i386 +libcxxtools-dev_2.1.1-1_i386 +libdacs-dev_1.4.27b-2_i386 +libdaemon-dev_0.14-2_i386 +libdancer-xml0-dev_0.8.2.1-3_i386 +libdap-dev_3.11.1-11_i386 +libdapl-dev_2.0.19-1.1_i386 +libdaq-dev_0.6.2-2_i386 +libdar-dev_2.4.5.debian.1-1_i386 +libdatrie-dev_0.2.5-3_i386 +libdawgdic-dev_0.4.3-1_all +libdb++-dev_5.1.6_i386 +libdb-dev_5.1.6_i386 +libdb-java-dev_5.1.6_i386 +libdb-sql-dev_5.1.6_i386 +libdb4o-cil-dev_8.0.184.15484+dfsg-2_all +libdb5.1++-dev_5.1.29-5_i386 +libdb5.1-dev_5.1.29-5_i386 +libdb5.1-java-dev_5.1.29-5_i386 +libdb5.1-sql-dev_5.1.29-5_i386 +libdb5.1-stl-dev_5.1.29-5_i386 +libdballe-dev_5.18-1_i386 +libdballef-dev_5.18-1_i386 +libdbaudiolib0-dev_0.9.8-6.2_i386 +libdbi-dev_0.8.4-6_i386 +libdbus-1-dev_1.6.8-1+deb7u1_i386 +libdbus-c++-dev_0.9.0-6_i386 +libdbus-glib-1-dev_0.100.2-1_i386 +libdbus-glib1.0-cil-dev_0.5.0-4_all +libdbus-ocaml-dev_0.29-1+b3_i386 +libdbus1.0-cil-dev_0.7.0-5_all +libdbusada0.2-dev_0.2-2_i386 +libdbusmenu-glib-dev_0.6.2-1_i386 +libdbusmenu-gtk-dev_0.6.2-1_i386 +libdbusmenu-gtk3-dev_0.6.2-1_i386 +libdbusmenu-jsonloader-dev_0.6.2-1_i386 +libdbusmenu-qt-dev_0.9.0-1_i386 +libdc-dev_0.3.24~svn3121-2_i386 +libdc1394-22-dev_2.2.0-2_i386 +libdca-dev_0.0.5-5_i386 +libdcerpc-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcerpc-server-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcmtk2-dev_3.6.0-12_i386 +libdconf-dbus-1-dev_0.12.1-3_i386 +libdconf-dev_0.12.1-3_i386 +libddccontrol-dev_0.4.2-10_i386 +libdds-dev_2.1.2+ddd105-1_i386 +libdebconf-kde-dev_0.2-2_i386 +libdebconfclient0-dev_0.182_i386 +libdebian-installer4-dev_0.87_i386 +libdebug0-dev_0.4.4-1.1_i386 +libdecodeqr-dev_0.9.3-6.2_i386 +libdee-dev_1.0.10-3_i386 +libderiving-ocaml-dev_0.1.1a-3+b1_i386 +libderiving-ocsigen-ocaml-dev_0.3c-1_i386 +libdesktop-agnostic-dev_0.3.92+dfsg-1_i386 +libdessert0.87-dev_0.87.2-1_i386 +libdevhelp-dev_3.4.1-1_i386 +libdevil-dev_1.7.8-6.1+b1_i386 +libdevmapper-dev_2:1.02.74-8_i386 +libdhash-dev_0.1.3-2_i386 +libdiagnostics-dev_0.3.3-1.3_i386 +libdianewcanvas2-dev_0.6.10-5.4_i386 +libdieharder-dev_3.31.1-4_i386 +libdiet-admin2.8-dev_2.8.0-1+b1_i386 +libdiet-client2.8-dev_2.8.0-1+b1_i386 +libdiet-dagda2.8-dev_2.8.0-1+b1_i386 +libdiet-sed2.8-dev_2.8.0-1+b1_i386 +libdime-dev_0.20030921-2_all +libdirac-dev_1.0.2-6_i386 +libdirectfb-dev_1.2.10.0-5_i386 +libdisasm-dev_0.23-5_i386 +libdiscid0-dev_0.2.2-3_i386 +libdiscover-dev_2.1.2-5.2_i386 +libdispatch-dev_0~svn197-3.1_i386 +libdisplaymigration0-dev_0.28-10_i386 +libdistorm64-dev_1.7.30-1_i386 +libdivecomputer-dev_0.1.0-3_i386 +libdjconsole-dev_0.1.3-1_i386 +libdjvulibre-dev_3.5.25.3-1_i386 +libdkim-dev_1:1.0.21-3_i386 +libdlm-dev_3.0.12-3.2+deb7u2_i386 +libdlmcontrol-dev_3.0.12-3.2+deb7u2_i386 +libdlrestrictions-dev_0.15.3_i386 +libdm0-dev_2.2.10-1_i386 +libdmalloc-dev_5.5.2-5_i386 +libdmapsharing-3.0-dev_2.9.15-1_i386 +libdmraid-dev_1.0.0.rc16-4.2_i386 +libdmtcpaware-dev_1.2.5-1_i386 +libdmtx-dev_0.7.2-2+build1_i386 +libdmx-dev_1:1.1.2-1+deb7u1_i386 +libdnet-dev_2.60_i386 +libdockapp-dev_1:0.5.0-3_i386 +libdolfin1.0-dev_1.0.0-7_i386 +libdoodle-dev_0.7.0-5_i386 +libdose2-ocaml-dev_1.4.2-4+b3_i386 +libdose3-ocaml-dev_3.0.2-3_i386 +libdotconf-dev_1.0.13-3_i386 +libdpkg-dev_1.16.12_i386 +libdpm-dev_1.8.2-1+b2_i386 +libdrawtk-dev_2.0-2_i386 +libdrizzle-dev_1:7.1.36-stable-1_all +libdrizzledmessage-dev_1:7.1.36-stable-1_i386 +libdrm-dev_2.4.40-1~deb7u2_i386 +libdrumstick-dev_0.5.0-3_i386 +libdsdp-dev_5.8-9.1_i386 +libdshconfig1-dev_0.20.13-1_i386 +libdspam7-dev_3.10.1+dfsg-11_i386 +libdssi-ocaml-dev_0.1.0-1+b1_i386 +libdssialsacompat-dev_1.0.8a-1_i386 +libdtools-ocaml-dev_0.3.0-1_i386 +libdts-dev_0.0.5-5_i386 +libdumb1-dev_1:0.9.3-5.4_i386 +libdumbnet-dev_1.12-3.1_i386 +libdune-common-dev_2.2.0-1_i386 +libdune-geometry-dev_2.2.0-1_i386 +libdune-grid-dev_2.2.0-1_i386 +libdune-istl-dev_2.2.0-1_all +libdune-localfunctions-dev_2.2.0-1_all +libduo-dev_1.8-1_i386 +libduppy-ocaml-dev_0.4.2-1+b2_i386 +libdv4-dev_1.0.0-6_i386 +libdvb-dev_0.5.5.1-5.1_i386 +libdvbcsa-dev_1.1.0-2_i386 +libdvbpsi-dev_0.2.2-1_i386 +libdvdnav-dev_4.2.0+20120524-2_i386 +libdvdread-dev_4.2.0+20120521-2_i386 +libdw-dev_0.152-1+wheezy1_i386 +libdwarf-dev_20120410-2_i386 +libdx4-dev_1:4.4.4-4+b2_i386 +libdxflib-dev_2.2.0.0-8_i386 +libdynamite-dev_0.1.1-2_i386 +libeasy-format-ocaml-dev_1.0.0-1+b2_i386 +libeb16-dev_4.4.3-6_i386 +libebackend1.2-dev_3.4.4-3_i386 +libebml-dev_1.2.2-2_i386 +libebook1.2-dev_3.4.4-3_i386 +libecal1.2-dev_3.4.4-3_i386 +libecasoundc-dev_2.9.0-1_i386 +libecasoundc2.2-dev_2.9.0-1_all +libechonest-dev_1.2.1-1_i386 +libecm-dev_6.4.2-1_i386 +libecore-dev_1.2.0-2_i386 +libecpg-dev_9.1.13-0wheezy1_i386 +libecryptfs-dev_99-1_i386 +libedac-dev_0.18-1_i386 +libedata-book1.2-dev_3.4.4-3_i386 +libedata-cal1.2-dev_3.4.4-3_i386 +libedataserver1.2-dev_3.4.4-3_i386 +libedataserverui-3.0-dev_3.4.4-3_i386 +libedbus-dev_1.2.0-1_i386 +libedit-dev_2.11-20080614-5_i386 +libeditline-dev_1.12-6_i386 +libedje-dev_1.2.0-1_i386 +libee-dev_0.4.1-1_i386 +libeegdev-dev_0.2-3_i386 +libeet-dev_1.6.0-1_i386 +libefreet-dev_1.2.0-1_i386 +libegl1-mesa-dev_8.0.5-4+deb7u2_i386 +libeigen2-dev_2.0.17-1_i386 +libeigen3-dev_3.1.0-1_i386 +libeina-dev_1.2.0-2_i386 +libelektra-cpp-dev_0.7.1-1_i386 +libelektra-dev_0.7.1-1_i386 +libelektratools-dev_0.7.1-1_i386 +libelemental-dev_1.2.0-8_i386 +libelementary-dev_0.7.0.55225-1_i386 +libelf-dev_0.152-1+wheezy1_i386 +libelfg0-dev_0.8.13-3_i386 +libeliom-ocaml-dev_2.2.2-1_i386 +libelk0-dev_3.99.8-2_i386 +libelmer-dev_6.1.0.svn.5396.dfsg2-2_i386 +libembryo-dev_1.2.0-1_i386 +libemos-dev_000382+dfsg-2_i386 +libenca-dev_1.13-4_i386 +libenchant-dev_1.6.0-7_i386 +libenet-dev_1.3.3-2_i386 +libepc-dev_0.4.4-1_i386 +libepc-ui-dev_0.4.4-1_i386 +libepr-api2-dev_2.2-2_all +libepsilon-dev_0.9.1-2_i386 +libept-dev_1.0.9_i386 +libepub-dev_0.2.1-2+b1_i386 +liberis-1.3-dev_1.3.19-5_i386 +liberuby-dev_1.0.5-2.1_i386 +libescpr-dev_1.1.1-2_i386 +libesd0-dev_0.2.41-10+b1_i386 +libesmtp-dev_1.0.6-1+b1_i386 +libespeak-dev_1.46.02-2_i386 +libestools2.1-dev_1:2.1~release-5_i386 +libestr-dev_0.1.1-2_i386 +libethos-dev_0.2.2-3_i386 +libethos-ui-dev_0.2.2-3_i386 +libetpan-dev_1.0-5_i386 +libetsf-io-dev_1.0.3-4+b1_i386 +libeurodec1-dev_20061220+dfsg3-2_i386 +libev-dev_1:4.11-1_i386 +libev-libevent-dev_1:4.11-1_all +libeval0-dev_0.29.6-2_i386 +libevas-dev_1.2.0-2_i386 +libevd-0.1-dev_0.1.20-2_i386 +libevent-dev_2.0.19-stable-3_i386 +libeventdb-dev_0.90-5_i386 +libevince-dev_3.4.0-3.1_i386 +libevocosm-dev_4.0.2-2.1_i386 +libevs-dev_1.4.2-3_i386 +libevtlog-dev_0.2.12-5_i386 +libewf-dev_20100226-1+b1_i386 +libexchangemapi-1.0-dev_3.4.4-1_i386 +libexempi-dev_2.2.0-1_i386 +libexif-dev_0.6.20-3_i386 +libexif-gtk-dev_0.3.5-5_i386 +libexiv2-dev_0.23-1_i386 +libexo-1-dev_0.6.2-5_i386 +libexodusii-dev_5.14.dfsg.1-2+b1_i386 +libexosip2-dev_3.6.0-4_i386 +libexpat-ocaml-dev_0.9.1+debian1-7+b2_i386 +libexpat1-dev_2.1.0-1+deb7u1_i386 +libexpect-ocaml-dev_0.0.2-1+b6_i386 +libexplain-dev_0.52.D002-1_i386 +libextlib-ocaml-dev_1.5.2-1+b1_i386 +libextractor-dev_1:0.6.3-5_i386 +libextractor-java-dev_0.6.0-6_i386 +libexttextcat-dev_3.2.0-2_i386 +libextunix-ocaml-dev_0.0.5-2_i386 +libeztrace-dev_0.7-2-4_i386 +libf2c2-dev_20090411-2_i386 +libfaad-dev_2.7-8_i386 +libfaad-ocaml-dev_0.3.0-1+b1_i386 +libfacile-ocaml-dev_1.1-8+b1_i386 +libfaifa-dev_0.2~svn82-1_i386 +libfakekey-dev_0.1-7_i386 +libfam-dev_2.7.0-17_i386 +libfann-dev_2.1.0~beta~dfsg-8_i386 +libfarstream-0.1-dev_0.1.2-1_i386 +libfastjet-dev_3.0.2+dfsg-2_i386 +libfastjet-fortran-dev_3.0.2+dfsg-2_i386 +libfastjetplugins-dev_3.0.2+dfsg-2_i386 +libfastjettools-dev_3.0.2+dfsg-2_i386 +libfauhdli-dev_20110812-1_i386 +libfcgi-dev_2.4.0-8.1_i386 +libfdt-dev_1.3.0-4_i386 +libfence-dev_3.0.12-3.2+deb7u2_i386 +libffado-dev_2.0.99+svn2171-2_i386 +libffcall1-dev_1.10+cvs20100619-2_i386 +libffi-dev_3.0.10-3_i386 +libffindex0-dev_0.9.6.1-1_i386 +libffmpegthumbnailer-dev_2.0.7-2_i386 +libffms2-dev_2.17-1_i386 +libfftw3-dev_3.3.2-3.1_i386 +libfftw3-mpi-dev_3.3.2-3.1_i386 +libfields-camlp4-dev_107.01-1+b2_i386 +libfileutils-ocaml-dev_0.4.2-1+b2_i386 +libfindlib-ocaml-dev_1.3.1-1_i386 +libfiredns-dev_0.9.12+dfsg-3_i386 +libfirestring-dev_0.9.12-8_i386 +libfishsound1-dev_1.0.0-1.1_i386 +libfiu-dev_0.90-3_i386 +libfixposix-dev_20110316.git47f17f7-1_i386 +libfko0-dev_2.0.0rc2-2+deb7u2_i386 +libflac++-dev_1.2.1-6_i386 +libflac-dev_1.2.1-6_i386 +libflac-ocaml-dev_0.1.1-1_i386 +libflake-dev_0.11-2_i386 +libflann-dev_1.7.1-4_i386 +libflatzebra-dev_0.1.5-4+b1_i386 +libflickrnet-cil-dev_1:2.2.0-4_all +libflorist2011-dev_2011-1_i386 +libflowcanvas-dev_0.7.1+dfsg0-0.2_i386 +libfltk1.1-dev_1.1.10-14_i386 +libfltk1.3-dev_1.3.0-8_i386 +libfluidsynth-dev_1.1.5-2_i386 +libfm-dev_0.1.17-2.1_i386 +libfolia1-dev_0.9-2_i386 +libfolks-dev_0.6.9-1+b1_i386 +libfolks-eds-dev_0.6.9-1+b1_i386 +libfolks-telepathy-dev_0.6.9-1+b1_i386 +libfontconfig1-dev_2.9.0-7.1_i386 +libfontenc-dev_1:1.1.1-1_i386 +libfontforge-dev_0.0.20120101+git-2_i386 +libforms-dev_1.0.93sp1-2_i386 +libformsgl-dev_1.0.93sp1-2_i386 +libfox-1.6-dev_1.6.45-1_i386 +libfprint-dev_1:0.4.0-4-gdfff16f-4_i386 +libfreecell-solver-dev_3.12.0-1_i386 +libfreefem++-dev_3.19.1-1_i386 +libfreefem-dev_3.5.8-5_i386 +libfreehdl0-dev_0.0.7-1.1_i386 +libfreeimage-dev_3.15.1-1+b1_i386 +libfreeipmi-dev_1.1.5-3_i386 +libfreenect-dev_1:0.1.2+dfsg-6_i386 +libfreeradius-dev_2.1.12+dfsg-1.2_i386 +libfreerdp-dev_1.0.1-1.1+deb7u3_i386 +libfreetype6-dev_2.4.9-1.1_i386 +libfreexl-dev_1.0.0b-1_i386 +libfribidi-dev_0.19.2-3_i386 +libfs-dev_2:1.0.4-1+deb7u1_i386 +libfso-glib-dev_2012.05.24.1-1.1_i386 +libfsobasics-dev_0.11.0-1.1_i386 +libfsoframework-dev_0.11.0-1.1_i386 +libfsoresource-dev_0.11.0-1.1_i386 +libfsosystem-dev_0.11.0-1_i386 +libfsotransport-dev_0.11.1-2.1_i386 +libfsplib-dev_0.11-2_i386 +libfstrcmp-dev_0.4.D001-1+deb7u1_i386 +libftdi-dev_0.20-1+b1_i386 +libftdipp-dev_0.20-1+b1_i386 +libftgl-dev_2.1.3~rc5-4_i386 +libfuntools-dev_1.4.4-3_i386 +libfuse-dev_2.9.0-2+deb7u1_i386 +libfuzzy-dev_2.7-2_i386 +libfxt-dev_0.2.6-2_i386 +libg15-dev_1.2.7-2_i386 +libg15daemon-client-dev_1.9.5.3-8.2_i386 +libg15render-dev_1.3.0~svn316-2.2_i386 +libg2-dev_0.72-2.1_i386 +libg3d-dev_0.0.8-17_i386 +libga-dev_2.4.7-3_i386 +libgadap-dev_2.0-1_i386 +libgadu-dev_1:1.11.2-1+deb7u1_i386 +libgail-3-dev_3.4.2-7_i386 +libgail-dev_2.24.10-2_i386 +libgalax-ocaml-dev_1.1-10+b3_i386 +libgambc4-dev_4.2.8-1.1_i386 +libgamin-dev_0.1.10-4.1_i386 +libgammu-dev_1.31.90-1+b1_i386 +libganglia1-dev_3.3.8-1+nmu1_i386 +libganv-dev_0~svn4468~dfsg0-1_i386 +libgarcon-1-0-dev_0.1.12-1_i386 +libgarmin-dev_0~svn320-3_i386 +libgatos-dev_0.0.5-19_i386 +libgavl-dev_1.4.0-1_i386 +libgavl-ocaml-dev_0.1.4-1+b1_i386 +libgbm-dev_8.0.5-4+deb7u2_i386 +libgc-dev_1:7.1-9.1_i386 +libgcal-dev_0.9.6-3_i386 +libgccxml-dev_0.9.0+cvs20120420-4_i386 +libgcgi-dev_0.9.5.dfsg-7_i386 +libgcj12-dev_4.6.3-1_i386 +libgcj13-dev_4.7.2-3_i386 +libgck-1-dev_3.4.1-3_i386 +libgconf-bridge-dev_0.1-2.2_i386 +libgconf2-dev_3.2.5-1+build1_i386 +libgconf2.0-cil-dev_2.24.2-3_all +libgconfmm-2.6-dev_2.28.0-1_i386 +libgcr-3-dev_3.4.1-3_i386 +libgcroots-dev_0.8.5-2.1_i386 +libgcrypt11-dev_1.5.0-5+deb7u1_i386 +libgctp-dev_1.0-1_i386 +libgd-gd2-noxpm-ocaml-dev_1.0~alpha5-5_i386 +libgd2-noxpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgda-5.0-dev_5.0.3-2_i386 +libgdal-dev_1.9.0-3.1_i386 +libgdal1-dev_1.9.0-3.1_all +libgdata-cil-dev_2.1.0.0-1_all +libgdata-dev_0.12.0-1_i386 +libgdb-dev_7.4.1+dfsg-0.1_i386 +libgdbm-dev_1.8.3-11_i386 +libgdchart-gd2-noxpm-dev_0.11.5-7+b1_i386 +libgdchart-gd2-xpm-dev_0.11.5-7+b1_i386 +libgdcm2-dev_2.2.0-14.1_i386 +libgdf-dev_0.1.2-2_i386 +libgdict-1.0-dev_3.4.0-2_i386 +libgdk-pixbuf2.0-dev_2.26.1-1_i386 +libgdkcutter-pixbuf-dev_1.1.7-1.2_i386 +libgdl-3-dev_3.4.2-1_i386 +libgdome2-cpp-smart-dev_0.2.6-6+b1_i386 +libgdome2-dev_0.8.1+debian-4.1_i386 +libgdome2-ocaml-dev_0.2.6-6+b1_i386 +libgdu-dev_3.0.2-3_i386 +libgdu-gtk-dev_3.0.2-3_i386 +libgeant321-2-dev_1:3.21.14.dfsg-10_i386 +libgearman-dev_0.33-2_i386 +libgecode-dev_3.7.3-1_i386 +libgeda-dev_1:1.6.2-4.3_i386 +libgee-dev_0.6.4-2_i386 +libgegl-dev_0.2.0-2+nmu1_i386 +libgeier-dev_0.13-1+b1_i386 +libgenders0-dev_1.18-1_i386 +libgenome-1.3-0-dev_1.3.1-3_i386 +libgensec-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libgeoclue-dev_0.12.0-4_i386 +libgeocode-glib-dev_0.99.0-1_i386 +libgeographiclib-dev_1.21-1_i386 +libgeoip-dev_1.4.8+dfsg-3_i386 +libgeomview-dev_1.9.4-3_i386 +libgeos++-dev_3.3.3-1.1_i386 +libgeos-dev_3.3.3-1.1_i386 +libgeotiff-dev_1.3.0+dfsg-3_i386 +libgeotranz3-dev_3.1-2.1_i386 +libges-0.10-dev_0.10.1-2_i386 +libgetdata-dev_0.7.3-6_i386 +libgetfem++-dev_4.1.1+dfsg1-11_i386 +libgetopt++-dev_0.0.2-p22-3_i386 +libgetopt-ocaml-dev_0.0.20040811-10+b3_i386 +libgettext-ocaml-dev_0.3.4-1+b2_i386 +libgexiv2-dev_0.4.1-3_i386 +libgfarm-dev_2.4.1-1.1_i386 +libgflags-dev_2.0-1_i386 +libgfshare-dev_1.0.5-2_i386 +libghc-acid-state-dev_0.6.3-1+b2_i386 +libghc-active-dev_0.1.0.1-2+b2_i386 +libghc-adjunctions-dev_2.4.0.2-1_i386 +libghc-aeson-dev_0.6.0.2-1+b4_i386 +libghc-agda-dev_2.3.0.1-2_i386 +libghc-algebra-dev_2.1.1.2-1_i386 +libghc-alut-dev_2.1.0.2-4+b1_i386 +libghc-ami-dev_0.1-1+b5_i386 +libghc-ansi-terminal-dev_0.5.5-3+b1_i386 +libghc-ansi-wl-pprint-dev_0.6.4-1+b1_i386 +libghc-arrows-dev_0.4.4.0-3+b1_i386 +libghc-asn1-data-dev_0.6.1.3-2+b3_i386 +libghc-attempt-dev_0.4.0-1+b2_i386 +libghc-attoparsec-conduit-dev_0.4.0.1-1_i386 +libghc-attoparsec-dev_0.10.1.1-2+b1_i386 +libghc-attoparsec-enumerator-dev_0.3-3+b3_i386 +libghc-augeas-dev_0.6.1-1_i386 +libghc-authenticate-dev_1.2.1.1-2+b1_i386 +libghc-base-unicode-symbols-dev_0.2.2.3-1+b1_i386 +libghc-base16-bytestring-dev_0.1.1.4-2+b1_i386 +libghc-base64-bytestring-dev_0.1.1.1-2_i386 +libghc-bifunctors-dev_0.1.3.3-1+b1_i386 +libghc-binary-shared-dev_0.8.1-1+b1_i386 +libghc-bindings-dsl-dev_1.0.15-1+b1_i386 +libghc-bindings-gpgme-dev_0.1.4-1_i386 +libghc-bindings-libzip-dev_0.10-2_i386 +libghc-bitarray-dev_0.0.1-2+b1_i386 +libghc-blaze-builder-conduit-dev_0.4.0.2-1_i386 +libghc-blaze-builder-dev_0.3.1.0-1+b2_i386 +libghc-blaze-builder-enumerator-dev_0.2.0.4-1+b1_i386 +libghc-blaze-html-dev_0.4.3.1-3+b2_i386 +libghc-blaze-markup-dev_0.5.1.0-1_i386 +libghc-blaze-textual-dev_0.2.0.6-2+b2_i386 +libghc-bloomfilter-dev_1.2.6.8-1_i386 +libghc-boolean-dev_0.0.1-2+b1_i386 +libghc-boomerang-dev_1.3.1-1_i386 +libghc-brainfuck-dev_0.1-2+b2_i386 +libghc-byteorder-dev_1.0.3-2+b1_i386 +libghc-bytestring-lexing-dev_0.4.0-1+b1_i386 +libghc-bytestring-mmap-dev_0.2.2-2+b1_i386 +libghc-bytestring-nums-dev_0.3.5-2+b1_i386 +libghc-bytestring-show-dev_0.3.5.1-1+b1_i386 +libghc-bzlib-dev_0.5.0.3-2+b1_i386 +libghc-cabal-file-th-dev_0.2.2-1_i386 +libghc-cairo-dev_0.12.3-1+b1_i386 +libghc-case-insensitive-dev_0.4.0.1-2+b2_i386 +libghc-categories-dev_1.0.3-1+b1_i386 +libghc-cautious-file-dev_1.0.1-1_i386 +libghc-cereal-conduit-dev_0.5-1+b1_i386 +libghc-cereal-dev_0.3.5.2-1_i386 +libghc-certificate-dev_1.2.3-2_i386 +libghc-cgi-dev_3001.1.8.2-2+b3_i386 +libghc-chart-dev_0.15-1+b2_i386 +libghc-chell-dev_0.3-1_i386 +libghc-citeproc-hs-dev_0.3.4-1+b4_i386 +libghc-clientsession-dev_0.7.5-3+b1_i386 +libghc-clock-dev_0.2.0.0-2+b1_i386 +libghc-cmdargs-dev_0.9.5-1+b1_i386 +libghc-colour-dev_2.3.3-1+b1_i386 +libghc-comonad-dev_1.1.1.5-1+b1_i386 +libghc-comonad-transformers-dev_2.1.1.1-1+b1_i386 +libghc-comonads-fd-dev_2.1.1.2-1+b1_i386 +libghc-conduit-dev_0.4.2-2_i386 +libghc-configfile-dev_1.0.6-4+b3_i386 +libghc-configurator-dev_0.2.0.0-1+b2_i386 +libghc-contravariant-dev_0.2.0.2-1+b1_i386 +libghc-convertible-dev_1.0.11.0-3+b3_i386 +libghc-cookie-dev_0.4.0-1+b3_i386 +libghc-cpphs-dev_1.13.3-2+b1_i386 +libghc-cprng-aes-dev_0.2.3-3+b4_i386 +libghc-cpu-dev_0.1.1-1_i386 +libghc-criterion-dev_0.6.0.1-3+b4_i386 +libghc-crypto-api-dev_0.10.2-1+b2_i386 +libghc-crypto-conduit-dev_0.3.2-1+b1_i386 +libghc-crypto-dev_4.2.4-1+b1_i386 +libghc-crypto-pubkey-types-dev_0.1.1-1+b3_i386 +libghc-cryptocipher-dev_0.3.5-1+b1_i386 +libghc-cryptohash-dev_0.7.5-1+b2_i386 +libghc-css-text-dev_0.1.1-3+b2_i386 +libghc-csv-conduit-dev_0.2-1_i386 +libghc-csv-dev_0.1.2-2+b3_i386 +libghc-curl-dev_1.3.7-1+b1_i386 +libghc-darcs-dev_2.8.1-1+b1_i386 +libghc-data-accessor-dev_0.2.2.2-1+b1_i386 +libghc-data-accessor-mtl-dev_0.2.0.3-1+b1_i386 +libghc-data-accessor-template-dev_0.2.1.9-1+b2_i386 +libghc-data-binary-ieee754-dev_0.4.2.1-3+b1_i386 +libghc-data-default-dev_0.4.0-1_i386 +libghc-data-inttrie-dev_0.0.7-1+b1_i386 +libghc-data-lens-dev_2.10.0-1+b1_i386 +libghc-data-memocombinators-dev_0.4.3-1+b1_i386 +libghc-dataenc-dev_0.14.0.3-1+b1_i386 +libghc-datetime-dev_0.2.1-3_i386 +libghc-dbus-dev_0.10.3-1_i386 +libghc-debian-dev_3.64-3_i386 +libghc-diagrams-cairo-dev_0.5.0.2-1_i386 +libghc-diagrams-core-dev_0.5.0.1-1+b1_i386 +libghc-diagrams-dev_0.5-2_all +libghc-diagrams-lib-dev_0.5-2_i386 +libghc-diff-dev_0.1.3-1+b1_i386 +libghc-digest-dev_0.0.1.0-1+b1_i386 +libghc-dimensional-dev_0.10.1.2-2+b1_i386 +libghc-directory-tree-dev_0.10.0-2+b1_i386 +libghc-distributive-dev_0.2.2-1+b1_i386 +libghc-dlist-dev_0.5-3+b1_i386 +libghc-download-curl-dev_0.1.3-3+b3_i386 +libghc-dpkg-dev_0.0.3-1_i386 +libghc-dyre-dev_0.8.7-1_i386 +libghc-edison-api-dev_1.2.1-18+b1_i386 +libghc-edison-core-dev_1.2.1.3-9+b1_i386 +libghc-edit-distance-dev_0.2.1-2_i386 +libghc-editline-dev_0.2.1.0-5+b1_i386 +libghc-ekg-dev_0.3.1.0-1+b2_i386 +libghc-email-validate-dev_0.2.8-1+b3_i386 +libghc-entropy-dev_0.2.1-2+b1_i386 +libghc-enumerator-dev_0.4.19-1+b1_i386 +libghc-erf-dev_2.0.0.0-2+b1_i386 +libghc-event-list-dev_0.1.0.1-1+b1_i386 +libghc-exception-transformers-dev_0.3.0.2-1+b1_i386 +libghc-executable-path-dev_0.0.3-1+b1_i386 +libghc-explicit-exception-dev_0.1.7-1+b1_i386 +libghc-failure-dev_0.2.0.1-1+b1_i386 +libghc-fast-logger-dev_0.0.2-1+b2_i386 +libghc-fastcgi-dev_3001.0.2.3-3+b3_i386 +libghc-fclabels-dev_1.1.3-1+b1_i386 +libghc-feed-dev_0.3.8-3_i386 +libghc-fgl-dev_5.4.2.4-2+b2_i386 +libghc-file-embed-dev_0.0.4.4-1_i386 +libghc-filemanip-dev_0.3.5.2-2+b2_i386 +libghc-filestore-dev_0.5-1_i386 +libghc-filesystem-conduit-dev_0.4.0-1_i386 +libghc-free-dev_2.1.1.1-1+b1_i386 +libghc-ftphs-dev_1.0.8-1+b3_i386 +libghc-gconf-dev_0.12.1-1+b1_i386 +libghc-gd-dev_3000.7.3-1_i386 +libghc-ghc-events-dev_0.4.0.0-2+b1_i386 +libghc-ghc-mtl-dev_1.0.1.1-1+b3_i386 +libghc-ghc-paths-dev_0.1.0.8-2+b1_i386 +libghc-ghc-syb-utils-dev_0.2.1.0-1+b3_i386 +libghc-gio-dev_0.12.3-1+b1_i386 +libghc-github-dev_0.4.0-2_i386 +libghc-gitit-dev_0.10.0.1-1+b1_i386 +libghc-glade-dev_0.12.1-1+b3_i386 +libghc-glfw-dev_0.5.0.1-1+b1_i386 +libghc-glib-dev_0.12.2-1+b1_i386 +libghc-glut-dev_2.1.2.2-1_i386 +libghc-gnuidn-dev_0.2-2+b2_i386 +libghc-gnutls-dev_0.1.2-1+b1_i386 +libghc-gsasl-dev_0.3.4-1+b1_i386 +libghc-gstreamer-dev_0.12.1-1+b2_i386 +libghc-gtk-dev_0.12.3-1+b2_i386 +libghc-gtkglext-dev_0.12.1-1+b3_i386 +libghc-gtksourceview2-dev_0.12.3-1+b3_i386 +libghc-haddock-dev_2.10.0-1+b2_i386 +libghc-hakyll-dev_3.2.7.2-1+b5_i386 +libghc-hamlet-dev_1.0.1.3-1+b1_i386 +libghc-happstack-dev_7.0.0-1+b1_i386 +libghc-happstack-server-dev_7.0.1-1+b1_i386 +libghc-harp-dev_0.4-3+b1_i386 +libghc-hashable-dev_1.1.2.3-1+b2_i386 +libghc-hashed-storage-dev_0.5.9-2+b2_i386 +libghc-hashmap-dev_1.3.0.1-1+b2_i386 +libghc-hashtables-dev_1.0.1.4-1+b1_i386 +libghc-haskeline-dev_0.6.4.7-1+b1_i386 +libghc-haskell-lexer-dev_1.0-3+b1_i386 +libghc-haskell-src-dev_1.0.1.5-1+b2_i386 +libghc-haskelldb-dev_2.1.1-5+b1_i386 +libghc-haskelldb-hdbc-dev_2.1.0-4_i386 +libghc-haskelldb-hdbc-odbc-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-postgresql-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-sqlite3-dev_2.1.0-3_i386 +libghc-haskore-dev_0.2.0.3-2_i386 +libghc-hastache-dev_0.3.3-2+b3_i386 +libghc-haxml-dev_1:1.22.5-2+b2_i386 +libghc-haxr-dev_3000.8.5-1+b3_i386 +libghc-hcard-dev_0.0-2+b2_i386 +libghc-hcwiid-dev_0.0.1-3+b1_i386 +libghc-hdbc-dev_2.3.1.1-1+b3_i386 +libghc-hdbc-odbc-dev_2.2.3.0-5+b3_i386 +libghc-hdbc-postgresql-dev_2.3.2.1-1+b3_i386 +libghc-hdbc-sqlite3-dev_2.3.3.0-1+b3_i386 +libghc-hfuse-dev_0.2.4.1-1_i386 +libghc-highlighting-kate-dev_0.5.1-1_i386 +libghc-hinotify-dev_0.3.2-1+b1_i386 +libghc-hint-dev_0.3.3.4-2+b4_i386 +libghc-hipmunk-dev_5.2.0.8-1+b1_i386 +libghc-hjavascript-dev_0.4.7-3+b1_i386 +libghc-hjscript-dev_0.5.0-3+b2_i386 +libghc-hjsmin-dev_0.1.1-1+b2_i386 +libghc-hlint-dev_1.8.28-1+b3_i386 +libghc-hoauth-dev_0.3.4-1+b1_i386 +libghc-hostname-dev_1.0-4+b1_i386 +libghc-hs-bibutils-dev_4.12-5+b2_i386 +libghc-hs3-dev_0.5.6-2+b4_i386 +libghc-hscolour-dev_1.19-3+b1_i386 +libghc-hscurses-dev_1.4.1.0-1+b2_i386 +libghc-hsemail-dev_1.7.1-2+b3_i386 +libghc-hsh-dev_2.0.3-6+b3_i386 +libghc-hslogger-dev_1.1.4+dfsg1-2+b3_i386 +libghc-hsp-dev_0.6.1-2+b3_i386 +libghc-hspec-dev_1.1.0-1+b1_i386 +libghc-hsql-dev_1.8.1-4_i386 +libghc-hsql-mysql-dev_1.8.1-4+b1_i386 +libghc-hsql-odbc-dev_1.8.1.1-2_i386 +libghc-hsql-postgresql-dev_1.8.1-3_i386 +libghc-hsql-sqlite3-dev_1.8.1-2_i386 +libghc-hssyck-dev_0.50-2+b2_i386 +libghc-hstringtemplate-dev_0.6.8-1_i386 +libghc-hsx-dev_0.9.1-3_i386 +libghc-html-conduit-dev_0.0.1-2_i386 +libghc-html-dev_1.0.1.2-5+b1_i386 +libghc-http-conduit-dev_1.4.1.6-3_i386 +libghc-http-date-dev_0.0.2-1+b2_i386 +libghc-http-dev_1:4000.2.3-1+b2_i386 +libghc-http-types-dev_0.6.11-1_i386 +libghc-hunit-dev_1.2.4.2-2+b1_i386 +libghc-hxt-cache-dev_9.0.2-2+b3_i386 +libghc-hxt-charproperties-dev_9.1.1-2+b1_i386 +libghc-hxt-curl-dev_9.1.1-1+b4_i386 +libghc-hxt-dev_9.2.2-2+b3_i386 +libghc-hxt-http-dev_9.1.4-2+b3_i386 +libghc-hxt-regex-xmlschema-dev_9.0.4-2+b3_i386 +libghc-hxt-relaxng-dev_9.1.4-1+b3_i386 +libghc-hxt-tagsoup-dev_9.1.1-1+b4_i386 +libghc-hxt-unicode-dev_9.0.2-2+b1_i386 +libghc-hxt-xpath-dev_9.1.2-1+b4_i386 +libghc-hxt-xslt-dev_9.1.1-1+b3_i386 +libghc-iconv-dev_0.4.1.0-2+b1_i386 +libghc-ieee754-dev_0.7.3-1+b1_i386 +libghc-ifelse-dev_0.85-4+b1_i386 +libghc-io-choice-dev_0.0.1-1+b3_i386 +libghc-io-storage-dev_0.3-2+b1_i386 +libghc-iospec-dev_0.2.5-1+b2_i386 +libghc-irc-dev_0.5.0.0-1+b3_i386 +libghc-iteratee-dev_0.8.8.2-2+b1_i386 +libghc-ixset-dev_1.0.3-2+b1_i386 +libghc-json-dev_0.5-2+b2_i386 +libghc-keys-dev_2.1.3.2-1+b1_i386 +libghc-knob-dev_0.1.1-1_i386 +libghc-lambdabot-utils-dev_4.2.1-3+b3_i386 +libghc-language-c-dev_0.4.2-2+b2_i386 +libghc-language-haskell-extract-dev_0.2.1-4+b1_i386 +libghc-language-javascript-dev_0.5.4-1+b2_i386 +libghc-largeword-dev_1.0.1-2+b1_i386 +libghc-lazysmallcheck-dev_0.6-1+b1_i386 +libghc-ldap-dev_0.6.6-4.1+b1_i386 +libghc-leksah-server-dev_0.12.0.4-3_i386 +libghc-libtagc-dev_0.12.0-2+b1_i386 +libghc-libxml-sax-dev_0.7.2-2+b1_i386 +libghc-libzip-dev_0.10-1+b2_i386 +libghc-lifted-base-dev_0.1.1-1+b1_i386 +libghc-listlike-dev_3.1.4-1+b1_i386 +libghc-llvm-base-dev_3.0.1.0-1_i386 +libghc-llvm-dev_3.0.1.0-1+b1_i386 +libghc-logict-dev_0.5.0.1-1+b1_i386 +libghc-ltk-dev_0.12.0.0-2+b1_i386 +libghc-maccatcher-dev_2.1.5-2+b3_i386 +libghc-magic-dev_1.0.8-8+b1_i386 +libghc-markov-chain-dev_0.0.3.2-1+b1_i386 +libghc-math-functions-dev_0.1.1.0-2+b2_i386 +libghc-maths-dev_0.4.3-1+b1_i386 +libghc-maybet-dev_0.1.2-3+b2_i386 +libghc-mbox-dev_0.1-2+b1_i386 +libghc-memotrie-dev_0.5-1_i386 +libghc-mersenne-random-dev_1.0.0.1-2+b1_i386 +libghc-midi-dev_0.2.0.1-1+b1_i386 +libghc-mime-mail-dev_0.4.1.1-2+b3_i386 +libghc-missingh-dev_1.1.0.3-6+b3_i386 +libghc-mmap-dev_0.5.7-2+b1_i386 +libghc-monad-control-dev_0.3.1.3-1+b1_i386 +libghc-monad-loops-dev_0.3.2.0-1_i386 +libghc-monad-par-dev_0.1.0.3-2+b1_i386 +libghc-monadcatchio-mtl-dev_0.3.0.4-2+b2_i386 +libghc-monadcatchio-transformers-dev_0.3.0.0-2+b1_i386 +libghc-monadcryptorandom-dev_0.4.1-1+b2_i386 +libghc-monadrandom-dev_0.1.6-2+b2_i386 +libghc-monads-tf-dev_0.1.0.0-1+b2_i386 +libghc-monoid-transformer-dev_0.0.2-3+b1_i386 +libghc-mtl-dev_2.1.1-1_i386 +libghc-mtlparse-dev_0.1.2-2+b2_i386 +libghc-murmur-hash-dev_0.1.0.5-2+b1_i386 +libghc-mwc-random-dev_0.11.0.0-4+b1_i386 +libghc-ncurses-dev_0.2.1-1+b1_i386 +libghc-netwire-dev_3.1.0-2+b5_i386 +libghc-network-conduit-dev_0.4.0.1-2_i386 +libghc-network-dev_2.3.0.13-1+b2_i386 +libghc-network-protocol-xmpp-dev_0.4.3-1_i386 +libghc-newtype-dev_0.2-1_i386 +libghc-non-negative-dev_0.1-2+b1_i386 +libghc-numbers-dev_2009.8.9-2+b1_i386 +libghc-numeric-quest-dev_0.2-1+b1_i386 +libghc-numinstances-dev_1.0-2+b1_i386 +libghc-numtype-dev_1.0-2+b1_i386 +libghc-oeis-dev_0.3.1-2+b3_i386 +libghc-openal-dev_1.3.1.3-4+b1_i386 +libghc-opengl-dev_2.2.3.1-1+b1_i386 +libghc-openpgp-asciiarmor-dev_0.1-1+b2_i386 +libghc-options-dev_0.1.1-1_i386 +libghc-pandoc-dev_1.9.4.2-2_i386 +libghc-pandoc-types-dev_1.9.1-1+b2_i386 +libghc-pango-dev_0.12.2-1+b2_i386 +libghc-parallel-dev_3.2.0.2-2+b1_i386 +libghc-parseargs-dev_0.1.3.2-2+b1_i386 +libghc-parsec2-dev_2.1.0.1-6+b1_i386 +libghc-parsec3-dev_3.1.2-1+b3_i386 +libghc-pastis-dev_0.1.2-2+b3_i386 +libghc-path-pieces-dev_0.1.0-1+b2_i386 +libghc-patience-dev_0.1.1-1_i386 +libghc-pcre-light-dev_0.4-3+b1_i386 +libghc-pem-dev_0.1.1-1+b3_i386 +libghc-persistent-dev_0.9.0.4-2_i386 +libghc-persistent-sqlite-dev_0.9.0.2-2_i386 +libghc-persistent-template-dev_0.9.0.2-1_i386 +libghc-polyparse-dev_1.7-1+b2_i386 +libghc-pool-conduit-dev_0.1.0.2-1_i386 +libghc-postgresql-libpq-dev_0.8.2-1_i386 +libghc-postgresql-simple-dev_0.1.4.3-1_i386 +libghc-pretty-show-dev_1.1.1-4+b1_i386 +libghc-primes-dev_0.2.1.0-2+b1_i386 +libghc-primitive-dev_0.4.1-1+b1_i386 +libghc-psqueue-dev_1.1-2+b1_i386 +libghc-puremd5-dev_2.1.0.3-2+b4_i386 +libghc-pwstore-fast-dev_2.2-2+b4_i386 +libghc-quickcheck1-dev_1.2.0.1-2+b1_i386 +libghc-quickcheck2-dev_2.4.2-1+b1_i386 +libghc-random-dev_1.0.1.1-1+b1_i386 +libghc-random-shuffle-dev_0.0.3-2+b2_i386 +libghc-ranged-sets-dev_0.3.0-2+b1_i386 +libghc-ranges-dev_0.2.4-2+b1_i386 +libghc-reactive-banana-dev_0.6.0.0-1+b3_i386 +libghc-readline-dev_1.0.1.0-3+b1_i386 +libghc-recaptcha-dev_0.1-4+b3_i386 +libghc-regex-base-dev_0.93.2-2+b2_i386 +libghc-regex-compat-dev_0.95.1-2+b1_i386 +libghc-regex-pcre-dev_0.94.2-2+b1_i386 +libghc-regex-posix-dev_0.95.1-2+b1_i386 +libghc-regex-tdfa-dev_1.1.8-2+b1_i386 +libghc-regex-tdfa-utf8-dev_1.0-5+b3_i386 +libghc-regexpr-dev_0.5.4-2+b2_i386 +libghc-representable-functors-dev_2.4.0.2-1+b1_i386 +libghc-representable-tries-dev_2.4.0.2-1_i386 +libghc-resource-pool-dev_0.2.1.0-2+b4_i386 +libghc-resourcet-dev_0.3.2.1-1+b1_i386 +libghc-rsa-dev_1.2.1.0-1+b1_i386 +libghc-safe-dev_0.3.3-1+b1_i386 +libghc-safecopy-dev_0.6.1-1+b1_i386 +libghc-sdl-dev_0.6.3-1+b1_i386 +libghc-sdl-gfx-dev_0.6.0-3+b1_i386 +libghc-sdl-image-dev_0.6.1-3+b1_i386 +libghc-sdl-mixer-dev_0.6.1-3+b1_i386 +libghc-sdl-ttf-dev_0.6.1-3+b1_i386 +libghc-semigroupoids-dev_1.3.1.2-1+b1_i386 +libghc-semigroups-dev_0.8.3.2-1_i386 +libghc-sendfile-dev_0.7.6-1+b2_i386 +libghc-sha-dev_1.5.0.1-1_i386 +libghc-shakespeare-css-dev_1.0.1.2-1+b1_i386 +libghc-shakespeare-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-i18n-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-js-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-text-dev_1.0.0.2-1+b1_i386 +libghc-shellac-dev_0.9.5.1-2+b2_i386 +libghc-show-dev_0.4.1.2-1+b2_i386 +libghc-silently-dev_1.1.4-1+b2_i386 +libghc-simple-sendfile-dev_0.2.3-1+b2_i386 +libghc-simpleea-dev_0.1.1-2+b2_i386 +libghc-simpleirc-dev_0.2.1-2+b3_i386 +libghc-skein-dev_0.1.0.7-2+b1_i386 +libghc-smallcheck-dev_0.6-1+b1_i386 +libghc-smtpclient-dev_1.0.4-3+b3_i386 +libghc-snap-core-dev_0.8.1-1+b4_i386 +libghc-snap-server-dev_0.8.1.1-1_i386 +libghc-socks-dev_0.4.1-1+b4_i386 +libghc-split-dev_0.1.4.2-2_i386 +libghc-src-exts-dev_1.11.1-3+b1_i386 +libghc-statevar-dev_1.0.0.0-2+b1_i386 +libghc-static-hash-dev_0.0.1-3+b2_i386 +libghc-statistics-dev_0.10.1.0-2+b1_i386 +libghc-stm-dev_2.3-1_i386 +libghc-stream-dev_0.4.6-1+b1_i386 +libghc-strict-concurrency-dev_0.2.4.1-2+b1_i386 +libghc-strict-dev_0.3.2-2+b1_i386 +libghc-strptime-dev_1.0.6-1_i386 +libghc-svgcairo-dev_0.12.1-1+b2_i386 +libghc-syb-dev_0.3.6.1-1_i386 +libghc-syb-with-class-dev_0.6.1.3-1+b1_i386 +libghc-syb-with-class-instances-text-dev_0.0.1-3+b2_i386 +libghc-system-fileio-dev_0.3.8-1_i386 +libghc-system-filepath-dev_0.4.6-1+b2_i386 +libghc-tagged-dev_0.4.2.1-1_i386 +libghc-tagsoup-dev_0.12.6-1+b3_i386 +libghc-tagstream-conduit-dev_0.3.2-1_i386 +libghc-tar-dev_0.3.2.0-2+b1_i386 +libghc-template-dev_0.2.0.7-1+b1_i386 +libghc-temporary-dev_1.1.2.3-1+b1_i386 +libghc-terminfo-dev_0.3.2.3-1+b1_i386 +libghc-test-framework-dev_0.6-1+b1_i386 +libghc-test-framework-hunit-dev_0.2.7-1+b3_i386 +libghc-test-framework-quickcheck2-dev_0.2.12.1-1+b1_i386 +libghc-test-framework-th-dev_0.2.2-5_i386 +libghc-test-framework-th-prime-dev_0.0.5-1_i386 +libghc-testpack-dev_2.1.1-1+b2_i386 +libghc-texmath-dev_0.6.0.6-1+b2_i386 +libghc-text-dev_0.11.2.0-1_i386 +libghc-text-icu-dev_0.6.3.4-2+b2_i386 +libghc-tinyurl-dev_0.1.0-2+b3_i386 +libghc-tls-dev_0.9.5-1+b4_i386 +libghc-tls-extra-dev_0.4.6.1-2_i386 +libghc-tokyocabinet-dev_0.0.5-5+b3_i386 +libghc-transformers-base-dev_0.4.1-2+b2_i386 +libghc-transformers-dev_0.3.0.0-1_i386 +libghc-type-level-dev_0.2.4-5_i386 +libghc-uniplate-dev_1.6.7-1+b2_i386 +libghc-unix-bytestring-dev_0.3.5-2+b1_i386 +libghc-unix-compat-dev_0.3.0.1-1+b1_i386 +libghc-unixutils-dev_1.50-1+b1_i386 +libghc-unlambda-dev_0.1-2+b2_i386 +libghc-unordered-containers-dev_0.2.1.0-1_i386 +libghc-uri-dev_0.1.6-1+b2_i386 +libghc-url-dev_2.1.2-4+b1_i386 +libghc-utf8-light-dev_0.4.0.1-2+b1_i386 +libghc-utf8-string-dev_0.3.7-1+b1_i386 +libghc-utility-ht-dev_0.0.5.1-3+b1_i386 +libghc-uuagc-cabal-dev_1.0.2.0-1+b1_i386 +libghc-uuid-dev_1.2.3-2+b4_i386 +libghc-uulib-dev_0.9.14-2_i386 +libghc-vault-dev_0.2.0.0-1+b2_i386 +libghc-vector-algorithms-dev_0.5.4-1+b2_i386 +libghc-vector-dev_0.9.1-2+b1_i386 +libghc-vector-space-dev_0.8.1-1_i386 +libghc-vector-space-points-dev_0.1.1.0-1+b1_i386 +libghc-void-dev_0.5.5.1-2+b1_i386 +libghc-vte-dev_0.12.1-1+b3_i386 +libghc-vty-dev_4.7.0.14-1+b1_i386 +libghc-wai-app-file-cgi-dev_0.5.8-1+b4_i386 +libghc-wai-app-static-dev_1.2.0.3-1+b3_i386 +libghc-wai-dev_1.2.0.2-1+b2_i386 +libghc-wai-extra-dev_1.2.0.4-1_i386 +libghc-wai-logger-dev_0.1.4-1+b6_i386 +libghc-wai-logger-prefork-dev_0.1.3-1+b6_i386 +libghc-wai-test-dev_1.2.0.2-1_i386 +libghc-warp-dev_1.2.1.1-1_i386 +libghc-warp-tls-dev_1.2.0.4-1+b4_i386 +libghc-web-routes-dev_0.25.3-2+b3_i386 +libghc-webkit-dev_0.12.3-2+b1_i386 +libghc-weighted-regexp-dev_0.3.1.1-2+b1_i386 +libghc-x11-dev_1.5.0.1-1+b2_i386 +libghc-x11-xft-dev_0.3.1-1+b3_i386 +libghc-xdg-basedir-dev_0.2.1-2+b1_i386 +libghc-xhtml-dev_3000.2.1-1_i386 +libghc-xml-conduit-dev_0.7.0.2-1_i386 +libghc-xml-dev_1.3.12-1+b2_i386 +libghc-xml-types-dev_0.3.1-2+b2_i386 +libghc-xml2html-dev_0.1.2.3-1_i386 +libghc-xmonad-contrib-dev_0.10-4~deb7u1_i386 +libghc-xmonad-dev_0.10-4+b2_i386 +libghc-xss-sanitize-dev_0.3.2-1+b1_i386 +libghc-yaml-dev_0.7.0.2-1+b2_i386 +libghc-yaml-light-dev_0.1.4-2+b2_i386 +libghc-yesod-auth-dev_1.0.2.1-2+b2_i386 +libghc-yesod-core-dev_1.0.1.2-1+b3_i386 +libghc-yesod-default-dev_1.0.1.1-1+b1_i386 +libghc-yesod-dev_1.0.1.6-2+b3_i386 +libghc-yesod-form-dev_1.0.0.4-1+b1_i386 +libghc-yesod-json-dev_1.0.0.1-1+b3_i386 +libghc-yesod-markdown-dev_0.4.0-1+b3_i386 +libghc-yesod-persistent-dev_1.0.0.1-1+b1_i386 +libghc-yesod-routes-dev_1.0.1.2-1_i386 +libghc-yesod-static-dev_1.0.0.2-1+b3_i386 +libghc-yesod-test-dev_0.2.0.6-1_i386 +libghc-zip-archive-dev_0.1.1.7-3+b2_i386 +libghc-zlib-bindings-dev_0.1.0.1-1_i386 +libghc-zlib-conduit-dev_0.4.0.1-1_i386 +libghc-zlib-dev_0.5.3.3-1+b1_i386 +libghc-zlib-enum-dev_0.2.2.1-1+b1_i386 +libghc6-agda-dev_1:8_all +libghc6-alut-dev_1:8_all +libghc6-arrows-dev_1:8_all +libghc6-binary-dev_1:8_all +libghc6-binary-shared-dev_1:8_all +libghc6-bzlib-dev_1:8_all +libghc6-cairo-dev_1:8_all +libghc6-cautious-file-dev_1:8_all +libghc6-cgi-dev_1:8_all +libghc6-colour-dev_1:8_all +libghc6-configfile-dev_1:8_all +libghc6-convertible-dev_1:8_all +libghc6-cpphs-dev_1:8_all +libghc6-criterion-dev_1:8_all +libghc6-csv-dev_1:8_all +libghc6-curl-dev_1:8_all +libghc6-data-accessor-dev_1:8_all +libghc6-dataenc-dev_1:8_all +libghc6-datetime-dev_1:8_all +libghc6-debian-dev_1:8_all +libghc6-deepseq-dev_1:8_all +libghc6-diagrams-dev_1:8_all +libghc6-diff-dev_1:8_all +libghc6-digest-dev_1:8_all +libghc6-edison-api-dev_1:8_all +libghc6-edison-core-dev_1:8_all +libghc6-editline-dev_1:8_all +libghc6-erf-dev_1:8_all +libghc6-event-list-dev_1:8_all +libghc6-explicit-exception-dev_1:8_all +libghc6-fastcgi-dev_1:8_all +libghc6-feed-dev_1:8_all +libghc6-fgl-dev_1:8_all +libghc6-filemanip-dev_1:8_all +libghc6-filestore-dev_1:8_all +libghc6-ftphs-dev_1:8_all +libghc6-gconf-dev_1:8_all +libghc6-ghc-events-dev_1:8_all +libghc6-ghc-mtl-dev_1:8_all +libghc6-ghc-paths-dev_1:8_all +libghc6-gio-dev_1:8_all +libghc6-gitit-dev_1:8_all +libghc6-glade-dev_1:8_all +libghc6-glfw-dev_1:8_all +libghc6-glib-dev_1:8_all +libghc6-glut-dev_1:8_all +libghc6-gstreamer-dev_1:8_all +libghc6-gtk-dev_1:8_all +libghc6-gtkglext-dev_1:8_all +libghc6-gtksourceview2-dev_1:8_all +libghc6-haddock-dev_1:8_all +libghc6-happstack-dev_1:8_all +libghc6-happstack-server-dev_1:8_all +libghc6-harp-dev_1:8_all +libghc6-hashed-storage-dev_1:8_all +libghc6-haskeline-dev_1:8_all +libghc6-haskell-lexer-dev_1:8_all +libghc6-haskell-src-dev_1:8_all +libghc6-haskelldb-dev_1:8_all +libghc6-haskelldb-hdbc-dev_1:8_all +libghc6-haskelldb-hdbc-odbc-dev_1:8_all +libghc6-haskelldb-hdbc-postgresql-dev_1:8_all +libghc6-haskelldb-hdbc-sqlite3-dev_1:8_all +libghc6-haskore-dev_1:8_all +libghc6-haxml-dev_1:8_all +libghc6-haxr-dev_1:8_all +libghc6-hdbc-dev_1:8_all +libghc6-hdbc-odbc-dev_1:8_all +libghc6-hdbc-postgresql-dev_1:8_all +libghc6-hdbc-sqlite3-dev_1:8_all +libghc6-highlighting-kate-dev_1:8_all +libghc6-hint-dev_1:8_all +libghc6-hjavascript-dev_1:8_all +libghc6-hjscript-dev_1:8_all +libghc6-hoauth-dev_1:8_all +libghc6-hscolour-dev_1:8_all +libghc6-hscurses-dev_1:8_all +libghc6-hsemail-dev_1:8_all +libghc6-hsh-dev_1:8_all +libghc6-hslogger-dev_1:8_all +libghc6-hsp-dev_1:8_all +libghc6-hsql-dev_1:8_all +libghc6-hsql-mysql-dev_1:8_all +libghc6-hsql-odbc-dev_1:8_all +libghc6-hsql-postgresql-dev_1:8_all +libghc6-hsql-sqlite3-dev_1:8_all +libghc6-hstringtemplate-dev_1:8_all +libghc6-hsx-dev_1:8_all +libghc6-html-dev_1:8_all +libghc6-http-dev_1:8_all +libghc6-hunit-dev_1:8_all +libghc6-hxt-dev_1:8_all +libghc6-ifelse-dev_1:8_all +libghc6-irc-dev_1:8_all +libghc6-json-dev_1:8_all +libghc6-language-c-dev_1:8_all +libghc6-lazysmallcheck-dev_1:8_all +libghc6-ldap-dev_1:8_all +libghc6-leksah-server-dev_1:8_all +libghc6-llvm-dev_1:8_all +libghc6-ltk-dev_1:8_all +libghc6-magic-dev_1:8_all +libghc6-markov-chain-dev_1:8_all +libghc6-maybet-dev_1:8_all +libghc6-midi-dev_1:8_all +libghc6-missingh-dev_1:8_all +libghc6-mmap-dev_1:8_all +libghc6-monadcatchio-mtl-dev_1:8_all +libghc6-monoid-transformer-dev_1:8_all +libghc6-mtl-dev_1:8_all +libghc6-mwc-random-dev_1:8_all +libghc6-network-dev_1:8_all +libghc6-non-negative-dev_1:8_all +libghc6-openal-dev_1:8_all +libghc6-opengl-dev_1:8_all +libghc6-pandoc-dev_1:8_all +libghc6-pango-dev_1:8_all +libghc6-parallel-dev_1:8_all +libghc6-parsec2-dev_1:8_all +libghc6-parsec3-dev_1:8_all +libghc6-pcre-light-dev_1:8_all +libghc6-polyparse-dev_1:8_all +libghc6-pretty-show-dev_1:8_all +libghc6-primitive-dev_1:8_all +libghc6-quickcheck1-dev_1:8_all +libghc6-quickcheck2-dev_1:8_all +libghc6-recaptcha-dev_1:8_all +libghc6-regex-base-dev_1:8_all +libghc6-regex-compat-dev_1:8_all +libghc6-regex-posix-dev_1:8_all +libghc6-regex-tdfa-dev_1:8_all +libghc6-regex-tdfa-utf8-dev_1:8_all +libghc6-safe-dev_1:8_all +libghc6-sdl-dev_1:8_all +libghc6-sdl-gfx-dev_1:8_all +libghc6-sdl-image-dev_1:8_all +libghc6-sdl-mixer-dev_1:8_all +libghc6-sdl-ttf-dev_1:8_all +libghc6-sendfile-dev_1:8_all +libghc6-sha-dev_1:8_all +libghc6-smtpclient-dev_1:8_all +libghc6-split-dev_1:8_all +libghc6-src-exts-dev_1:8_all +libghc6-statistics-dev_1:8_all +libghc6-stm-dev_1:8_all +libghc6-stream-dev_1:8_all +libghc6-strict-concurrency-dev_1:8_all +libghc6-svgcairo-dev_1:8_all +libghc6-syb-with-class-dev_1:8_all +libghc6-syb-with-class-instances-text-dev_1:8_all +libghc6-tagsoup-dev_1:8_all +libghc6-tar-dev_1:8_all +libghc6-terminfo-dev_1:8_all +libghc6-testpack-dev_1:8_all +libghc6-texmath-dev_1:8_all +libghc6-text-dev_1:8_all +libghc6-tokyocabinet-dev_1:8_all +libghc6-transformers-dev_1:8_all +libghc6-type-level-dev_1:8_all +libghc6-uniplate-dev_1:8_all +libghc6-unix-compat-dev_1:8_all +libghc6-unixutils-dev_1:8_all +libghc6-url-dev_1:8_all +libghc6-utility-ht-dev_1:8_all +libghc6-uulib-dev_1:8_all +libghc6-vector-algorithms-dev_1:8_all +libghc6-vector-dev_1:8_all +libghc6-vte-dev_1:8_all +libghc6-vty-dev_1:8_all +libghc6-webkit-dev_1:8_all +libghc6-x11-dev_1:8_all +libghc6-x11-xft-dev_1:8_all +libghc6-xhtml-dev_1:8_all +libghc6-xml-dev_1:8_all +libghc6-xmonad-contrib-dev_1:8_all +libghc6-xmonad-dev_1:8_all +libghc6-zip-archive-dev_1:8_all +libghc6-zlib-dev_1:8_all +libghemical-dev_3.0.0-2_i386 +libgif-dev_4.1.6-10_i386 +libgiftiio-dev_1.0.9-1_i386 +libgig-dev_3.3.0-2_i386 +libgii1-dev_1:1.0.2-4.1_i386 +libgimp2.0-dev_2.8.2-2+deb7u1_i386 +libginac-dev_1.6.2-1_i386 +libginspx-dev_20050529-3.1_i386 +libgio2.0-cil-dev_2.22.3-2_all +libgirara-dev_0.1.2-3_i386 +libgirepository1.0-dev_1.32.1-1_i386 +libgjs-dev_1.32.0-5_i386 +libgkeyfile-cil-dev_0.1-4_all +libgksu2-dev_2.0.13~pre1-6_i386 +libgl1-mesa-dev_8.0.5-4+deb7u2_i386 +libgl1-mesa-swx11-dev_8.0.5-4+deb7u2_i386 +libgl2ps-dev_1.3.6-1_i386 +libglade2-dev_1:2.6.4-1_i386 +libglade2.0-cil-dev_2.12.10-5_i386 +libglademm-2.4-dev_2.6.7-2_i386 +libgladeui-1-dev_3.6.7-2.1_i386 +libgladeui-dev_3.12.1-1_i386 +libglbsp-dev_2.24-1_i386 +libglc-dev_0.7.2-5+b1_i386 +libgle3-dev_3.1.0-7_i386 +libgles1-mesa-dev_8.0.5-4+deb7u2_i386 +libgles2-mesa-dev_8.0.5-4+deb7u2_i386 +libglew-dev_1.7.0-3_i386 +libglewmx-dev_1.7.0-3_i386 +libglfw-dev_2.7.2-1_i386 +libglib2.0-cil-dev_2.12.10-5_i386 +libglib2.0-dev_2.33.12+really2.32.4-5_i386 +libglibmm-2.4-dev_2.32.1-1_i386 +libglide2-dev_2002.04.10ds1-7_i386 +libglide3-dev_2002.04.10ds1-7_i386 +libglm-dev_0.9.3.3+dfsg-0.1_all +libglobus-authz-callout-error-dev_2.2-1_i386 +libglobus-authz-dev_2.2-1_i386 +libglobus-callout-dev_2.2-1_i386 +libglobus-common-dev_14.7-2_i386 +libglobus-ftp-client-dev_7.3-1_i386 +libglobus-ftp-control-dev_4.4-1_i386 +libglobus-gass-cache-dev_8.1-2_i386 +libglobus-gass-copy-dev_8.4-1_i386 +libglobus-gass-server-ez-dev_4.3-1_i386 +libglobus-gass-transfer-dev_7.2-1_i386 +libglobus-gfork-dev_3.2-1_i386 +libglobus-gram-client-dev_12.4-1_i386 +libglobus-gram-job-manager-callout-error-dev_2.1-2_i386 +libglobus-gram-protocol-dev_11.3-1_i386 +libglobus-gridftp-server-control-dev_2.5-2_i386 +libglobus-gridftp-server-dev_6.10-2_i386 +libglobus-gridmap-callout-error-dev_1.2-2_i386 +libglobus-gsi-callback-dev_4.2-1_i386 +libglobus-gsi-cert-utils-dev_8.3-1_i386 +libglobus-gsi-credential-dev_5.3-1_i386 +libglobus-gsi-openssl-error-dev_2.1-2_i386 +libglobus-gsi-proxy-core-dev_6.2-1_i386 +libglobus-gsi-proxy-ssl-dev_4.1-2_i386 +libglobus-gsi-sysconfig-dev_5.2-1_i386 +libglobus-gss-assist-dev_8.5-1_i386 +libglobus-gssapi-error-dev_4.1-2_i386 +libglobus-gssapi-gsi-dev_10.6-1_i386 +libglobus-io-dev_9.3-1_i386 +libglobus-openssl-module-dev_3.2-1_i386 +libglobus-rls-client-dev_5.2-8_i386 +libglobus-rsl-dev_9.1-2_i386 +libglobus-scheduler-event-generator-dev_4.6-1_i386 +libglobus-usage-dev_3.1-2_i386 +libglobus-xio-dev_3.3-1_i386 +libglobus-xio-gsi-driver-dev_2.3-1_i386 +libglobus-xio-pipe-driver-dev_2.2-1_i386 +libglobus-xio-popen-driver-dev_2.3-1_i386 +libgloox-dev_1.0-1.1_i386 +libglpk-dev_4.45-1_i386 +libglrr-glib-dev_20050529-3.1_i386 +libglrr-gobject-dev_20050529-3.1_i386 +libglrr-gtk-dev_20050529-3.1_i386 +libglrr-widgets-dev_20050529-3.1_i386 +libglu1-mesa-dev_8.0.5-4+deb7u2_i386 +libglui-dev_2.36-4_i386 +libglw1-mesa-dev_8.0.0-1_i386 +libgme-dev_0.5.5-2_i386 +libgmerlin-avdec-dev_1.2.0~dfsg-1+b1_i386 +libgmerlin-dev_1.2.0~dfsg+1-1_i386 +libgmime-2.6-dev_2.6.10-1_i386 +libgmime2.6-cil-dev_2.6.10-1_all +libgmlib-dev_1.0.6-1_i386 +libgmm++-dev_4.1.1+dfsg1-11_all +libgmp-dev_2:5.0.5+dfsg-2_i386 +libgmp-ocaml-dev_20021123-17+b3_i386 +libgmp3-dev_2:5.0.5+dfsg-2_i386 +libgmpada3-dev_0.0.20120331-1_i386 +libgmt-dev_4.5.7-2_i386 +libgmtk-dev_1.0.6-1_i386 +libgnadecommon2-dev_1.6.2-9_i386 +libgnadeodbc2-dev_1.6.2-9_i386 +libgnadesqlite3-2-dev_1.6.2-9_i386 +libgnatprj4.6-dev_4.6.3-8_i386 +libgnatvsn4.6-dev_4.6.3-8_i386 +libgnelib-dev_0.75+svn20091130-1+b1_i386 +libgnet-dev_2.0.8-2.2_i386 +libgnokii-dev_0.6.30+dfsg-1+b1_i386 +libgnome-bluetooth-dev_3.4.2-1_i386 +libgnome-desktop-3-dev_3.4.2-1_i386 +libgnome-desktop-dev_2.32.1-2_i386 +libgnome-keyring-dev_3.4.1-1_i386 +libgnome-keyring1.0-cil-dev_1.0.0-4_i386 +libgnome-mag-dev_1:0.16.3-1_i386 +libgnome-media-profiles-dev_3.0.0-1_i386 +libgnome-menu-3-dev_3.4.2-5_i386 +libgnome-menu-dev_3.0.1-4_i386 +libgnome-speech-dev_1:0.4.25-5_i386 +libgnome-vfs2.0-cil-dev_2.24.2-3_all +libgnome-vfsmm-2.6-dev_2.26.0-1_i386 +libgnome2-dev_2.32.1-3_i386 +libgnome2.0-cil-dev_2.24.2-3_i386 +libgnomeada2.24.1-dev_2.24.1-7_i386 +libgnomecanvas2-dev_2.30.3-1.2_i386 +libgnomecanvasmm-2.6-dev_2.26.0-1_i386 +libgnomecups1.0-dev_0.2.3-5_i386 +libgnomedesktop2.0-cil-dev_2.26.0-8_all +libgnomekbd-dev_3.4.0.2-1_i386 +libgnomemm-2.6-dev_2.30.0-1_i386 +libgnomeprint2.2-dev_2.18.8-3_i386 +libgnomeprintui2.2-dev_2.18.6-3_i386 +libgnomeui-dev_2.24.5-2_i386 +libgnomeuimm-2.6-dev_2.28.0-1_i386 +libgnomevfs2-dev_1:2.24.4-2_i386 +libgnuift0-dev_0.1.14-12_i386 +libgnuplot-ocaml-dev_0.8.3-3_i386 +libgnustep-base-dev_1.22.1-4_i386 +libgnustep-dl2-dev_0.12.0-9+nmu1_i386 +libgnustep-gui-dev_0.20.0-3_i386 +libgnutls-dev_2.12.20-8+deb7u1_i386 +libgoa-1.0-dev_3.4.2-2_i386 +libgoffice-0.8-dev_0.8.17-1.2_i386 +libgofigure-dev_0.9.0-1+b2_i386 +libgoocanvas-dev_0.15-1_i386 +libgoocanvasmm-dev_0.15.4-1_i386 +libgoogle-perftools-dev_2.0-2_i386 +libgooglepinyin0-dev_0.1.2-1_i386 +libgpac-dev_0.5.0~dfsg0-1_i386 +libgpds-dev_1.5.1-6_i386 +libgpelaunch-dev_0.14-6_i386 +libgpepimc-dev_0.9-4_i386 +libgpeschedule-dev_0.17-4_i386 +libgpevtype-dev_0.50-6_i386 +libgpewidget-dev_0.117-6_i386 +libgpg-error-dev_1.10-3.1_i386 +libgpgme11-dev_1.2.0-1.4_i386 +libgphoto2-2-dev_2.4.14-2_i386 +libgpiv3-dev_0.6.1-4_i386 +libgpm-dev_1.20.4-6_i386 +libgpod-cil-dev_0.8.2-7_i386 +libgpod-dev_0.8.2-7_i386 +libgpod-nogtk-dev_0.8.2-7_i386 +libgportugol-dev_1.1-2_i386 +libgps-dev_3.6-4+deb7u1_i386 +libgraflib1-dev_20061220+dfsg3-2_i386 +libgrafx11-1-dev_20061220+dfsg3-2_i386 +libgrantlee-dev_0.1.4-1_i386 +libgraphicsmagick++1-dev_1.3.16-1.1_i386 +libgraphicsmagick1-dev_1.3.16-1.1_i386 +libgraphite-dev_1:2.3.1-0.2_i386 +libgraphite2-dev_1.1.3-1_i386 +libgraphviz-dev_2.26.3-14+deb7u1_i386 +libgretl1-dev_1.9.9-1_i386 +libgrib-api-dev_1.9.16-2+b1_i386 +libgrib2c-dev_1.2.2-2+b1_i386 +libgridsite-dev_1.7.16-1_i386 +libgrilo-0.1-dev_0.1.19-1_i386 +libgringotts-dev_1.2.10~pre3-1_i386 +libgrits-dev_0.7-1_i386 +libgrok-dev_1.20110708.1-4_i386 +libgrss-dev_0.5.0-1_i386 +libgs-dev_9.05~dfsg-6.3+deb7u1_i386 +libgsasl7-dev_1.8.0-2_i386 +libgsecuredelete-dev_0.2-1_i386 +libgsf-1-dev_1.14.21-2.1_i386 +libgsf-gnome-1-dev_1.14.21-2.1_i386 +libgsl0-dev_1.15+dfsg.2-2_i386 +libgsm0710-dev_1.2.2-2_i386 +libgsm0710mux-dev_0.11.2-1.1_i386 +libgsm1-dev_1.0.13-4_i386 +libgsmme-dev_1.10-13.2_i386 +libgsnmp0-dev_0.3.0-1.1_i386 +libgsql-dev_0.2.2-1.2+b1_i386 +libgss-dev_1.0.2-1_i386 +libgssdp-1.0-dev_0.12.2.1-2_i386 +libgssglue-dev_0.4-2_i386 +libgst-dev_3.2.4-2_i386 +libgstbuzztard-dev_0.5.0-2+deb7u1_i386 +libgstreamer-ocaml-dev_0.1.0-3+b1_i386 +libgstreamer-plugins-bad0.10-dev_0.10.23-7.1+deb7u1_i386 +libgstreamer-plugins-base0.10-dev_0.10.36-1.1_i386 +libgstreamer0.10-cil-dev_0.9.2-4_all +libgstreamer0.10-dev_0.10.36-1.2_i386 +libgstrtspserver-0.10-dev_0.10.8-3_i386 +libgtest-dev_1.6.0-2_i386 +libgtextutils-dev_0.6.2-1_i386 +libgtg-dev_0.2+dfsg-1_i386 +libgtk-3-dev_3.4.2-7_i386 +libgtk-sharp-beans2.0-cil-dev_2.14.1-3_all +libgtk-vnc-1.0-dev_0.5.0-3.1_i386 +libgtk-vnc-2.0-dev_0.5.0-3.1_i386 +libgtk2.0-cil-dev_2.12.10-5_i386 +libgtk2.0-dev_2.24.10-2_i386 +libgtkada2.24.1-dev_2.24.1-7_i386 +libgtkdatabox-0.9.1-1-dev_1:0.9.1.1-4_i386 +libgtkgl2.0-dev_2.0.1-2_i386 +libgtkglada2.24.1-dev_2.24.1-7_i386 +libgtkglarea-cil-dev_0.0.17-6_all +libgtkglext1-dev_1.2.0-2_i386 +libgtkglextmm-x11-1.2-dev_1.2.0-4.1_i386 +libgtkhex-3-dev_3.4.1-1_i386 +libgtkhotkey-dev_0.2.1-3_i386 +libgtkhtml-4.0-dev_4.4.4-1_i386 +libgtkhtml-editor-3.14-dev_3.32.2-2.1_i386 +libgtkhtml-editor-4.0-dev_4.4.4-1_i386 +libgtkhtml3.14-cil-dev_2.26.0-8_i386 +libgtkhtml3.14-dev_3.32.2-2.1_i386 +libgtkimageview-dev_1.6.4+dfsg-0.1_i386 +libgtkmathview-dev_0.8.0-8_i386 +libgtkmm-2.4-dev_1:2.24.2-1_i386 +libgtkmm-3.0-dev_3.4.2-1_i386 +libgtkpod-dev_2.1.2-1_i386 +libgtksourceview-3.0-dev_3.4.2-1_i386 +libgtksourceview2-cil-dev_2.26.0-8_i386 +libgtksourceview2.0-dev_2.10.4-1_i386 +libgtksourceviewmm-3.0-dev_3.2.0-1_i386 +libgtkspell-3-dev_3.0.0~hg20110814-1_i386 +libgtkspell-dev_2.0.16-1_i386 +libgtop2-dev_2.28.4-3_i386 +libgts-dev_0.7.6+darcs110121-1.1_i386 +libguac-dev_0.6.0-2_i386 +libgucharmap-2-90-dev_1:3.4.1.1-2.1_i386 +libgudev-1.0-dev_175-7.2_i386 +libgudev1.0-cil-dev_0.1-3_all +libguess-dev_1.1-1_i386 +libguestfs-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-gobject-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-ocaml-dev_1:1.18.1-1+deb7u3_i386 +libguichan-dev_0.8.2-10+b1_i386 +libgupnp-1.0-dev_0.18.4-1_i386 +libgupnp-av-1.0-dev_0.10.3-1_i386 +libgupnp-dlna-1.0-dev_0.6.6-1_i386 +libgupnp-igd-1.0-dev_0.2.1-2_i386 +libgusb-dev_0.1.3-5_i386 +libgutenprint-dev_5.2.9-1_i386 +libgutenprintui2-dev_5.2.9-1_i386 +libguytools2-dev_2.0.1-1.1_i386 +libgvnc-1.0-dev_0.5.0-3.1_i386 +libgweather-3-dev_3.4.1-1+build1_i386 +libgwenhywfar60-dev_4.3.3-1_i386 +libgwrap-runtime-dev_1.9.14-1.1_i386 +libgwyddion20-dev_2.28-2_i386 +libgxps-dev_0.2.2-2_i386 +libgyoto0-dev_0.0.3-5_i386 +libh323plus-dev_1.24.0~dfsg2-1_i386 +libhaildb-dev_2.3.2-1.2_i386 +libhal-dev_0.5.14-8_i386 +libhal-storage-dev_0.5.14-8_i386 +libhamlib++-dev_1.2.15.1-1_i386 +libhamlib-dev_1.2.15.1-1_i386 +libhandoff-dev_0.1-5_i386 +libhangul-dev_0.1.0-2_i386 +libharminv-dev_1.3.1-9_i386 +libhashkit-dev_1.0.8-1_i386 +libhawknl-dev_1.6.8+dfsg2-1_i386 +libhbaapi-dev_2.2.5-1_i386 +libhbalinux-dev_1.0.14-1_i386 +libhd-dev_16.0-2.2_i386 +libhdate-dev_1.6-1_i386 +libhdf4-alt-dev_4.2r4-13_i386 +libhdf4-dev_4.2r4-13_i386 +libhdf4g-dev_4.2r4-13_all +libhdf5-dev_1.8.8-9_i386 +libhdf5-mpi-dev_1.8.8-9_i386 +libhdf5-mpich2-dev_1.8.8-9_i386 +libhdf5-openmpi-dev_1.8.8-9_i386 +libhdf5-serial-dev_1.8.8-9_i386 +libhdfeos-dev_2.17v1.00.dfsg.1-3_i386 +libhdhomerun-dev_20120405-1_i386 +libhe5-hdfeos-dev_5.1.13.dfsg.1-3_i386 +libheartbeat2-dev_1:3.0.5-3_i386 +libhepmc-dev_2.06.09-1_i386 +libhepmcfio-dev_2.06.09-1_i386 +libhepmcinterface8-dev_8.1.65-1_i386 +libherwig59-2-dev_20061220+dfsg3-2_i386 +libhesiod-dev_3.0.2-21_i386 +libhfsp-dev_1.0.4-12_i386 +libhighgui-dev_2.3.1-11_i386 +libhippocanvas-dev_0.3.1-1.1_i386 +libhiredis-dev_0.10.1-7_i386 +libhivex-dev_1.3.6-2_i386 +libhivex-ocaml-dev_1.3.6-2_i386 +libhkl-dev_4.0.3-4_i386 +libhmsbeagle-dev_1.0-6_i386 +libhocr-dev_0.10.17-1+b2_i386 +libhpdf-dev_2.2.1-1_i386 +libhpmud-dev_3.12.6-3.1+deb7u1_i386 +libhsclient-dev_1.1.0-7-g1044a28-1_i386 +libhtmlcxx-dev_0.85-2_i386 +libhtp-dev_0.2.6-2_i386 +libhtsengine-dev_1.06-1_i386 +libhttp-ocaml-dev_0.1.5-1+b2_i386 +libhttrack-dev_3.46.1-1_i386 +libhunspell-dev_1.3.2-4_i386 +libhwloc-dev_1.4.1-4_i386 +libhx-dev_3.12.1-1_i386 +libhyantes-dev_1.3.0-1_i386 +libhyena-cil-dev_0.5-2_all +libhyphen-dev_2.8.3-2_i386 +libhypre-dev_2.8.0b-1_all +libhz-dev_0.3.16-3_i386 +libi2c-dev_3.1.0-2_all +libibcm-dev_1.0.4-1.1_i386 +libibcommon-dev_1.1.2-20090314-1_i386 +libibdm-dev_1.2-OFED-1.4.2-1.3_i386 +libibmad-dev_1.2.3-20090314-1.1_i386 +libibtk-dev_0.0.14-12_i386 +libibumad-dev_1.2.3-20090314-1.1_i386 +libibus-1.0-dev_1.4.1-9+deb7u1_i386 +libibus-qt-dev_1.3.1-2.1_i386 +libibverbs-dev_1.1.6-1_i386 +libical-dev_0.48-2_i386 +libicapapi-dev_1:0.1.6-1.1_i386 +libicc-dev_2.12+argyll1.4.0-8_i386 +libicc-utils-dev_1.6.4-1+b1_i386 +libice-dev_2:1.0.8-2_i386 +libicee-dev_1.2.0-6.1_i386 +libicns-dev_0.8.1-1_i386 +libiconv-hook-dev_0.0.20021209-10_i386 +libics-dev_1.5.2-3_i386 +libicu-dev_4.8.1.1-12+deb7u1_i386 +libid3-3.8.3-dev_3.8.3-15_i386 +libid3tag0-dev_0.15.1b-10_i386 +libident-dev_0.22-3_i386 +libidl-dev_0.8.14-0.2_i386 +libidn11-dev_1.25-2_i386 +libidn2-0-dev_0.8-2_i386 +libido-0.1-dev_0.3.4-1_i386 +libido3-0.1-dev_0.3.4-1_i386 +libidzebra-2.0-dev_2.0.44-3_i386 +libiec16022-dev_0.2.4-1_i386 +libiec61883-dev_1.2.0-0.1_i386 +libieee1284-3-dev_0.2.11-10_i386 +libifp-dev_1.0.0.2-5_i386 +libifstat-dev_1.1-8_i386 +libigraph0-dev_0.5.4-2_i386 +libigstk4-dev_4.4.0-2+b1_i386 +libijs-dev_0.35-8_i386 +libiksemel-dev_1.2-4_i386 +libilmbase-dev_1.0.1-4_i386 +libimdi-dev_1.4.0-8_i386 +libiml-dev_1.0.3-4.2_i386 +libimlib2-dev_1.4.5-1_i386 +libimobiledevice-dev_1.1.1-4_i386 +libindi-dev_0.9.1-2_i386 +libindicate-dev_0.6.92-1_i386 +libindicate-gtk-dev_0.6.92-1_i386 +libindicate-gtk0.1-cil-dev_0.6.92-1_all +libindicate-gtk3-dev_0.6.92-1_i386 +libindicate-qt-dev_0.2.5.91-5_i386 +libindicate0.1-cil-dev_0.6.92-1_all +libindicator-dev_0.5.0-1_i386 +libindicator-messages-status-provider-dev_0.6.0-1_i386 +libindicator3-dev_0.5.0-1_i386 +libindigo-dev_1.0.0-2_i386 +libinfinity-0.5-dev_0.5.2-6.1_i386 +libini-config-dev_0.1.3-2_i386 +libinifiles-ocaml-dev_1.2-2_i386 +libinnodb-dev_1.0.6.6750-1_i386 +libinotify-ocaml-dev_1.0-1+b3_i386 +libinotifytools0-dev_3.14-1_i386 +libinput-pad-dev_1.0.1-2_i386 +libinsighttoolkit3-dev_3.20.1+git20120521-3_i386 +libinstpatch-dev_1.0.0-3_i386 +libint-dev_1.1.4-1_i386 +libiodbc2-dev_3.52.7-2+deb7u1_i386 +libion-dev_3.0.1~dfsg1-1_i386 +libipa-hbac-dev_1.8.4-2_i386 +libipathverbs-dev_1.2-1_i386 +libipe-dev_7.1.2-1_i386 +libipmiconsole-dev_1.1.5-3_i386 +libipmidetect-dev_1.1.5-3_i386 +libipmimonitoring-dev_1.1.5-3_i386 +libipset-dev_6.12.1-1_i386 +libiptcdata0-dev_1.0.4-3_i386 +libircclient-dev_1.3+dfsg1-3_i386 +libirman-dev_0.4.4-2_i386 +libirrlicht-dev_1.7.3+dfsg1-4_i386 +libisajet758-3-dev_20061220+dfsg3-2_i386 +libiscsi-dev_1.4.0-3_i386 +libisl-dev_0.10-3_i386 +libiso9660-dev_0.83-4_i386 +libisoburn-dev_1.2.2-2_i386 +libisofs-dev_1.2.2-1_i386 +libitl-dev_0.7.0-3_i386 +libitl-gobject-dev_0.2-1_i386 +libitpp-dev_4.2-4_i386 +libitsol-dev_1.0.0-2_i386 +libivykis-dev_0.30.1-2_i386 +libiw-dev_30~pre9-8_i386 +libjack-dev_1:0.121.3+20120418git75e3e20b-2.1_i386 +libjack-jackd2-dev_1.9.8~dfsg.4+20120529git007cdc37-5_i386 +libjalali-dev_0.4.0-1.1_i386 +libjama-dev_1.2.4-2_all +libjana-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-ecal-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-gtk-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjansson-dev_2.3.1-2_i386 +libjasper-dev_1.900.1-13_i386 +libjaula-dev_1.4.0-3_i386 +libjavascriptcoregtk-1.0-dev_1.8.1-3.4_i386 +libjavascriptcoregtk-3.0-dev_1.8.1-3.4_i386 +libjbig-dev_2.0-2+deb7u1_i386 +libjbig2dec0-dev_0.11+20120125-1_i386 +libjconv-dev_2.8-6+b1_i386 +libjemalloc-dev_3.0.0-3_i386 +libjim-dev_0.73-3_i386 +libjpeg62-dev_6b1-3_i386 +libjpeg8-dev_8d-1_i386 +libjpgalleg4-dev_2:4.4.2-2.1_i386 +libjs-of-ocaml-dev_1.2-2_i386 +libjson-glib-dev_0.14.2-1_i386 +libjson-spirit-dev_4.04-1+b1_i386 +libjson-static-camlp4-dev_0.9.8-1+b5_i386 +libjson-wheel-ocaml-dev_1.0.6-2+b8_i386 +libjson0-dev_0.10-1.2_i386 +libjsoncpp-dev_0.6.0~rc2-3_i386 +libjte-dev_1.19-1_i386 +libjthread-dev_1.3.1-3_i386 +libjudy-dev_1.0.5-1_i386 +libjuman-dev_5.1-2.1_i386 +libk3b-dev_2.0.2-6_i386 +libkactivities-dev_4:4.8.4-1_i386 +libkakasi2-dev_2.3.5~pre1+cvs20071101-1_i386 +libkal-dev_0.9.0-1_i386 +libkarma-cil-dev_0.1.2-2.3_all +libkarma-dev_0.1.2-2.3_i386 +libkate-dev_0.4.1-1_i386 +libkaya-gd-dev_0.4.4-6_i386 +libkaya-gl-dev_0.4.4-6_i386 +libkaya-mysql-dev_0.4.4-6_i386 +libkaya-ncurses-dev_0.4.4-6_i386 +libkaya-ncursesw-dev_0.4.4-6_i386 +libkaya-pgsql-dev_0.4.4-6_i386 +libkaya-sdl-dev_0.4.4-6_i386 +libkaya-sqlite3-dev_0.4.4-6_i386 +libkcddb-dev_4:4.8.4-2_i386 +libkdcraw-dev_4:4.8.4-1_i386 +libkdeedu-dev_4:4.8.4-1_i386 +libkdegames-dev_4:4.8.4-3_i386 +libkdtree++-dev_0.7.0-2_all +libkernlib1-dev_20061220+dfsg3-2_i386 +libkexiv2-dev_4:4.8.4-1_i386 +libkeybinder-dev_0.2.2-4_i386 +libkeyutils-dev_1.5.5-3_i386 +libkibi-dev_0.1-1_i386 +libkipi-dev_4:4.8.4-1_i386 +libkiten-dev_4:4.8.4-1_i386 +libklatexformula3-dev_3.2.6-1_i386 +libklibc-dev_2.0.1-3.1_i386 +libkmfl-dev_0.9.8-1_i386 +libkmflcomp-dev_0.9.8-1_i386 +libkml-dev_1.3.0~r863-4.1_i386 +libkmod-dev_9-3_i386 +libkokyu-dev_6.0.3+dfsg-0.1_i386 +libkonq5-dev_4:4.8.4-2_i386 +libkonqsidebarplugin-dev_4:4.8.4-2_i386 +libkopete-dev_4:4.8.4-1+b1_i386 +libkosd2-dev_0.8.1-1_i386 +libkpathsea-dev_2012.20120628-4_i386 +libkqueue-dev_1.0.4-2_i386 +libkrb5-dev_1.10.1+dfsg-5+deb7u1_i386 +libksane-dev_4:4.8.4-1_i386 +libksba-dev_1.2.0-2_i386 +libktoblzcheck1-dev_1.39-1_i386 +libktorrent-dev_1.2.1-1_i386 +libktpcommoninternalsprivate-dev_0.4.0-1_i386 +libkvutils-dev_2.9.0-1_i386 +libkvutils2.2-dev_2.9.0-1_all +libkwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libkwwidgets1-dev_1.0.0~cvs20100930-8_i386 +libkxl0-dev_1.1.7-16_i386 +liblablgl-ocaml-dev_1.04-5+b3_i386 +liblablgtk-extras-ocaml-dev_1.0-1+b2_i386 +liblablgtk2-gl-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-gnome-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtkmathview-ocaml-dev_0.7.8-6+b1_i386 +liblablgtksourceview2-ocaml-dev_2.14.2+dfsg-3_i386 +libladr-dev_0.0.200902a-2.1_i386 +libladspa-ocaml-dev_0.1.4-1+b1_i386 +liblapack-dev_3.4.1+dfsg-1+deb70u1_i386 +liblapacke-dev_3.4.1+dfsg-1+deb70u1_i386 +liblas-dev_1.2.1-5+b1_i386 +liblash-compat-dev_1+dfsg0-3_all +liblasi-dev_1.1.0-1_i386 +liblasso3-dev_2.3.6-2_i386 +liblastfm-dev_0.4.0~git20090710-2_i386 +liblastfm-ocaml-dev_0.3.0-2+b6_i386 +liblcgdm-dev_1.8.2-1+b2_i386 +liblcms1-dev_1.19.dfsg-1.2_i386 +liblcms2-dev_2.2+git20110628-2.2+deb7u1_i386 +libldap-ocaml-dev_2.1.8-8+b9_i386 +libldap2-dev_2.4.31-1+nmu2_i386 +libldb-dev_1:1.1.6-1_i386 +libldns-dev_1.6.13-1_i386 +libledit-ocaml-dev_2.03-1+b2_i386 +liblensfun-dev_0.2.5-2_i386 +libleptonica-dev_1.69-3.1_i386 +libleveldb-dev_0+20120530.gitdd0d562-1_i386 +liblfc-dev_1.8.2-1+b2_i386 +liblhapdf-dev_5.8.7+repack-1_i386 +liblhasa-dev_0.0.7-2_i386 +liblicense-dev_0.8.1-3_i386 +liblightdm-gobject-dev_1.2.2-4_i386 +liblightdm-qt-dev_1.2.2-4_i386 +liblilv-dev_0.14.2~dfsg0-4_i386 +liblinear-dev_1.8+dfsg-1_i386 +liblinebreak2-dev_2.1-1_i386 +liblink-grammar4-dev_4.7.4-2_i386 +liblinphone-dev_3.5.2-10_i386 +liblip-dev_2.0.0-1.1_i386 +liblircclient-dev_0.9.0~pre1-1_i386 +liblistaller-glib-dev_0.5.5-2_i386 +liblivemedia-dev_2012.05.17-1_i386 +libllvm-2.9-ocaml-dev_2.9+dfsg-7_i386 +libllvm-3.0-ocaml-dev_3.0-10_i386 +libllvm-3.1-ocaml-dev_3.1-1_i386 +libllvm-ocaml-dev_1:3.0-14+nmu2_i386 +liblo-dev_0.26~repack-7_i386 +liblo-ocaml-dev_0.1.0-1+b1_i386 +liblo10k1-dev_1.0.25-2_i386 +libloadpng4-dev_2:4.4.2-2.1_i386 +liblockdev1-dev_1.0.3-1.5_i386 +liblockfile-dev_1.09-5_i386 +liblodo3.0-dev_3.0.2+dfsg-4+b1_i386 +liblog4ada2-dev_1.2-3_i386 +liblog4c-dev_1.2.1-3_i386 +liblog4cplus-dev_1.0.4-1_i386 +liblog4cpp5-dev_1.0-4_i386 +liblog4cxx10-dev_0.10.0-1.2_i386 +liblog4net-cil-dev_1.2.10+dfsg-6_all +liblog4shib-dev_1.0.4-1_i386 +liblog4tango4-dev_7.2.6+dfsg-14_i386 +liblogforwarderutils2-dev_2.7-1_i386 +liblognorm-dev_0.3.4-1_i386 +liblogservicecomponentbase2-dev_2.7-1_i386 +liblogservicetoolbase2-dev_2.7-1_i386 +liblogsys-dev_1.4.2-3_i386 +liblogthread-dev_3.0.12-3.2+deb7u2_i386 +libloki-dev_0.1.7-3_i386 +libloudmouth1-dev_1.4.3-9_i386 +liblouis-dev_2.4.1-1_i386 +liblouisutdml-dev_2.2.0-1_i386 +liblouisxml-dev_2.4.0-3_i386 +liblowpan-dev_0.2.2-2.1_all +liblpsolve55-dev_5.5.0.13-7_i386 +liblqr-1-0-dev_0.4.1-2_i386 +liblrdf0-dev_0.4.0-5_i386 +liblrm2-dev_1.0.9+hg2665-1_i386 +liblrs-dev_0.42c-1+b1_i386 +liblscp-dev_0.5.6-6_i386 +libltcsmpte-dev_0.4.4-1_i386 +libltdl-dev_2.4.2-1.1_i386 +liblttctl-dev_0.89-05122011-1_i386 +liblttd-dev_0.89-05122011-1_i386 +liblttng-ust-dev_2.0.4-1_i386 +liblttoolbox3-3.1-0-dev_3.1.0-1.1_i386 +liblttvtraceread-2.6-dev_0.12.38-21032011-1+b1_i386 +liblua5.1-0-dev_5.1.5-4_i386 +liblua5.1-apr-dev_0.23.2-1_all +liblua5.1-bitop-dev_1.0.2-1_all +liblua5.1-cgi-dev_5.1.4+dfsg-2_all +liblua5.1-copas-dev_1.1.6-5_all +liblua5.1-curl-dev_0.3.0-7_all +liblua5.1-cyrussasl-dev_1.0.0-4_all +liblua5.1-event-dev_0.4.1-2_all +liblua5.1-expat-dev_1.2.0-5+deb7u1_all +liblua5.1-filesystem-dev_1.5.0+16+g84f1af5-1_all +liblua5.1-leg-dev_0.1.2-8_all +liblua5.1-logging-dev_1.2.0-1_all +liblua5.1-lpeg-dev_0.10.2-5_all +liblua5.1-md5-dev_1.1.2-6_all +liblua5.1-oocairo-dev_1.4-1.2_i386 +liblua5.1-oopango-dev_1.1-1_i386 +liblua5.1-orbit-dev_2.2.0+dfsg1-1_all +liblua5.1-posix-dev_5.1.19-2_all +liblua5.1-rex-onig-dev_2.6.0-2_all +liblua5.1-rex-pcre-dev_2.6.0-2_all +liblua5.1-rex-posix-dev_2.6.0-2_all +liblua5.1-rings-dev_1.2.3-1_all +liblua5.1-rrd-dev_1.4.7-2_i386 +liblua5.1-sec-dev_0.4.1-1_all +liblua5.1-soap-dev_3.0-3_all +liblua5.1-socket-dev_2.0.2-8_all +liblua5.1-sql-mysql-dev_2.3.0-1+build0_all +liblua5.1-sql-postgres-dev_2.3.0-1+build0_all +liblua5.1-sql-sqlite3-dev_2.3.0-1+build0_all +liblua5.1-svn-dev_0.4.0-7_all +liblua5.1-wsapi-fcgi-dev_1.5-3_all +liblua5.1-xmlrpc-dev_1.2.1-5_all +liblua5.1-zip-dev_1.2.3-11_all +liblua5.2-dev_5.2.1-3_i386 +liblua50-dev_5.0.3-6_i386 +libluabind-dev_0.9.1+dfsg-5_i386 +liblualib50-dev_5.0.3-6_i386 +liblunar-1-dev_2.0.1-2.2_i386 +liblunar-date-dev_2.4.0-1_i386 +liblv2dynparam1-dev_2-5_i386 +liblvm2-dev_2.02.95-8_i386 +liblwipv6-dev_1.5a-2_i386 +liblwt-glib-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ssl-ocaml-dev_2.3.2-1+b3_i386 +liblz-dev_1.3-2_i386 +liblzma-dev_5.1.1alpha+20120614-2_i386 +liblzo2-dev_2.06-1_i386 +libm17n-dev_1.6.3-2_i386 +libm17n-im-config-dev_0.9.0-3_i386 +libm4ri-dev_0.0.20080521-2_i386 +libmaa-dev_1.3.1-1_i386 +libmad-ocaml-dev_0.4.4-1+b1_i386 +libmad0-dev_0.15.1b-7_i386 +libmadlib-dev_1.3.0-2.1_i386 +libmagic-dev_5.11-2+deb7u3_i386 +libmagic-ocaml-dev_0.7.3-5+b3_i386 +libmagick++-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickcore-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickwand-dev_8:6.7.7.10-5+deb7u3_i386 +libmagics++-dev_2.14.11-4_i386 +libmailutils-dev_1:2.99.97-3_i386 +libmalaga-dev_7.12-4_i386 +libmaloc-dev_0.2-2.3_i386 +libmapi-dev_1:1.0-3_i386 +libmapiadmin-dev_1:1.0-3_i386 +libmapipp-dev_1:1.0-3_i386 +libmapiproxy-dev_1:1.0-3_i386 +libmapistore-dev_1:1.0-3_i386 +libmapnik-dev_2.0.0+ds1-3_all +libmapnik2-dev_2.0.0+ds1-3+b4_i386 +libmarble-dev_4:4.8.4-3_i386 +libmarkdown2-dev_2.1.3-3_i386 +libmatchbox-dev_1.9-osso8-3_i386 +libmath++-dev_0.0.4-4_i386 +libmatheval-dev_1.1.8-1_i386 +libmathlib2-dev_20061220+dfsg3-2_i386 +libmatio-dev_1.3.4-4_i386 +libmatrixssl1.8-dev_1.8.8-1_i386 +libmatroska-dev_1.3.0-2_i386 +libmbt0-dev_3.2.8-1_i386 +libmcpp-dev_2.7.2-1.1_i386 +libmcrypt-dev_2.5.8-3.1_i386 +libmcs-dev_0.7.2-2.1_i386 +libmd3-dev_0.1.92-4_i386 +libmdc2-dev_0.10.7-1+b2_i386 +libmdds-dev_0.5.4-1_all +libmdsp-dev_0.11-10_i386 +libmeanwhile-dev_1.0.2-4_i386 +libmecab-dev_0.99.3-3_i386 +libmed-dev_3.0.3-3_i386 +libmedc-dev_3.0.3-3_i386 +libmediainfo-dev_0.7.58-1_i386 +libmediastreamer-dev_3.5.2-10_i386 +libmedimport-dev_3.0.3-3_i386 +libmeep-dev_1.1.1-8+deb7u1_i386 +libmeep-lam4-dev_1.1.1-10~deb7u1_i386 +libmeep-mpi-default-dev_1.1.1-10~deb7u1_i386 +libmeep-mpich2-dev_1.1.1-10~deb7u1_i386 +libmeep-openmpi-dev_1.1.1-9~deb7u2_i386 +libmelt-ocaml-dev_1.4.0-1_i386 +libmemcache-dev_1.4.0.rc2-1_i386 +libmemcached-dev_1.0.8-1_i386 +libmemphis-0.2-dev_0.2.3-2_i386 +libmenhir-ocaml-dev_20120123.dfsg-1_i386 +libmenu-cache1-dev_0.3.3-1_i386 +libmercator-0.3-dev_0.3.0-2_i386 +libmeschach-dev_1.2b-13_i386 +libmetacity-dev_1:2.34.3-4_i386 +libmgl-dev_1.11.2-17_i386 +libmhash-dev_0.9.9.9-1.1_i386 +libmicrohttpd-dev_0.9.20-1+deb7u1_i386 +libmigemo-dev_20110227-7_i386 +libmikmatch-ocaml-dev_1.0.4-1+b1_i386 +libmikmod2-dev_3.1.12-5_i386 +libmilter-dev_8.14.4-4_i386 +libmimedir-dev_0.5.1-4_i386 +libmimedir-gnome-dev_0.4.2-5_i386 +libmimelib1-dev_5:1.1.4-2_i386 +libmimetic-dev_0.9.7-3_i386 +libmimic-dev_1.0.4-2.1_i386 +libminc-dev_2.1.10-1+b1_i386 +libming-dev_1:0.4.4-1.1_i386 +libmini18n-dev_0.2.1-1_i386 +libminidjvu-dev_0.8.svn.2010.05.06+dfsg-0.2_i386 +libminiupnpc-dev_1.5-2_i386 +libmission-control-plugins-dev_1:5.12.3-1_i386 +libmkv-dev_0.6.5.1-1_i386 +libmlpcap-ocaml-dev_0.9-16_i386 +libmlpost-ocaml-dev_0.8.1-3_i386 +libmlt++-dev_0.8.0-4_i386 +libmlt-dev_0.8.0-4_i386 +libmlx4-dev_1.0.4-1_i386 +libmm-dev_1.4.2-4_i386 +libmm-ocaml-dev_0.2.0-1+b1_i386 +libmmpong0.9-dev_0.9.1-2.1_i386 +libmms-dev_0.6.2-3_i386 +libmng-dev_1.0.10-3_i386 +libmnl-dev_1.0.3-3_i386 +libmodbus-dev_3.0.3-1_i386 +libmodglue1-dev_1.17-2.1_all +libmodplug-dev_1:0.8.8.4-3+deb7u1+git20130828_all +libmoe-dev_1.5.8-1_i386 +libmongo-client-dev_0.1.5-1+deb7u1_i386 +libmono-2.0-dev_2.10.8.1-8_i386 +libmono-addins-cil-dev_0.6.2-2_all +libmono-addins-gui-cil-dev_0.6.2-2_all +libmono-addins-msbuild-cil-dev_0.6.2-2_all +libmono-cecil-cil-dev_0.9.5+dfsg-2_all +libmono-cecil-flowanalysis-cil-dev_0.1~vcs20110809.r1.b34edf6-2_all +libmono-cil-dev_2.10.8.1-8_all +libmono-reflection-cil-dev_1.0+git20110407+d2343843-2_all +libmono-uia-cil-dev_2.1-4_all +libmono-upnp-cil-dev_0.1.2-1_all +libmono-zeroconf-cil-dev_0.9.0-4_all +libmonogame-cil-dev_2.5.1+dfsg-3_all +libmopac7-dev_1.15-5_i386 +libmorph-dev_1:20090926_i386 +libmosquitto0-dev_0.15-2_all +libmosquittopp0-dev_0.15-2_all +libmount-dev_2.20.1-5.3_i386 +libmowgli-dev_1.0.0-1_i386 +libmozjs-dev_24.4.0esr-1~deb7u2_i386 +libmozjs185-dev_1.8.5-1.0.0+dfsg-4_i386 +libmp3lame-dev_3.99.5+repack1-3_i386 +libmp3lame-ocaml-dev_0.3.1-1+b1_i386 +libmp3splt-dev_0.7.2-2_i386 +libmp4v2-dev_2.0.0~dfsg0-1_i386 +libmpc-dev_0.9-4_i386 +libmpcdec-dev_2:0.1~r459-4_i386 +libmpd-dev_0.20.0-1.1_i386 +libmpdclient-dev_2.3-1_i386 +libmpeg2-4-dev_0.4.1-3_i386 +libmpeg3-dev_1.5.4-5_i386 +libmpfi-dev_1.5.1-1_i386 +libmpfr-dev_3.1.0-5_i386 +libmpg123-dev_1.14.4-1_i386 +libmpich2-dev_1.4.1-4.2_i386 +libmpikmeans-dev_1.5-1+b1_i386 +libmrml1-dev_0.1.14-12_i386 +libmrmpi-dev_1.0~20110620.dfsg-2_i386 +libmrss0-dev_0.19.2-3_i386 +libmsgpack-dev_0.5.7-2_i386 +libmsn-dev_4.2-2_i386 +libmsv-dev_0.0.0-1_i386 +libmtbl-dev_0.2-1_i386 +libmtcp-dev_1.2.5-1_i386 +libmtdev-dev_1.1.2-1_i386 +libmthca-dev_1.0.6-1_i386 +libmtp-dev_1.1.3-35-g0ece104-5_i386 +libmudflap0-4.4-dev_4.4.7-2_i386 +libmudflap0-4.6-dev_4.6.3-14_i386 +libmudflap0-4.7-dev_4.7.2-5_i386 +libmulticobex1-dev_0.23-1.1_i386 +libmumps-dev_4.10.0.dfsg-3_i386 +libmumps-ptscotch-dev_4.10.0.dfsg-3_i386 +libmumps-scotch-dev_4.10.0.dfsg-3_i386 +libmumps-seq-dev_4.10.0.dfsg-3_i386 +libmunge-dev_0.5.10-1_i386 +libmuparser-dev_2.1.0-3_i386 +libmupdf-dev_0.9-2_i386 +libmupen64plus-dev_1.99.5-6_all +libmuroar-dev_0.1.8-2_i386 +libmusic-dev_1.0.7-1.2_i386 +libmusicbrainz3-dev_3.0.2-2.1_i386 +libmusicbrainz5-dev_5.0.1-2_i386 +libmutter-dev_3.4.1-5_i386 +libmx-dev_1.4.6-1_i386 +libmxml-dev_2.6-2_i386 +libmyproxy-dev_5.6-1_i386 +libmysql++-dev_3.1.0-2+b1_i386 +libmysql-cil-dev_6.4.3-2_all +libmysql-ocaml-dev_1.1.1-1_i386 +libmysqlclient-dev_5.5.35+dfsg-0+wheezy1_i386 +libmysqlcppconn-dev_1.1.0-4+b1_i386 +libmysqld-dev_5.5.35+dfsg-0+wheezy1_i386 +libmythes-dev_2:1.2.2-1_i386 +libnabrit-dev_0.4.1-1_i386 +libnacl-dev_20110221-4_i386 +libnacore-dev_0.4.0-3_i386 +libnanohttp-dev_1.1.0-17.1_i386 +libnatpmp-dev_20110808-3_i386 +libnautilus-extension-dev_3.4.2-1+build1_i386 +libnbio-dev_0.30-1_i386 +libncap-dev_1.9.2-1+b2_i386 +libncbi6-dev_6.1.20120620-2_i386 +libncp-dev_2.2.6-9_i386 +libncurses5-dev_5.9-10_i386 +libncursesada2-dev_5.9.20110404-7_i386 +libncursesw5-dev_5.9-10_i386 +libndesk-dbus-glib1.0-cil-dev_0.4.1-4_all +libndesk-dbus1.0-cil-dev_0.6.0-6_all +libndr-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libndr-standard-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libnecpp-dev_1.5.0+cvs20101003-2.1_i386 +libneon27-dev_0.29.6-3_i386 +libneon27-gnutls-dev_0.29.6-3_i386 +libnes-dev_1.1.3-1_i386 +libnet1-dev_1.1.4-2.1_i386 +libnet6-1.3-dev_1:1.3.14-1_i386 +libnetcdf-dev_1:4.1.3-6+b1_i386 +libnetcf-dev_0.1.9-2_i386 +libnetclasses-dev_1.06.dfsg-5+b3_i386 +libnetfilter-conntrack-dev_1.0.1-1_i386 +libnetfilter-cttimeout-dev_1.0.0-1_i386 +libnetfilter-log-dev_1.0.0-1_i386 +libnetfilter-queue-dev_0.0.17-1_i386 +libnethttpd-ocaml-dev_3.5.1-1_i386 +libnetpbm10-dev_2:10.0-15+b1_i386 +libnetpbm9-dev_2:10.0-15+b1_i386 +libnetsvcs-dev_6.0.3+dfsg-0.1_i386 +libnewlib-dev_1.18.0-6.2_i386 +libnewmat10-dev_1.10.4-5_i386 +libnewt-dev_0.52.14-11.1_i386 +libnewtonsoft-json-cil-dev_4.5r6-1_all +libnexus0-dev_4.2.1-svn1614-1+b2_i386 +libnfnetlink-dev_1.0.0-1.1_i386 +libnfo-dev_1.0.1-1_i386 +libnfs-dev_1.3.0-2_i386 +libnfsidmap-dev_0.25-4_i386 +libnice-dev_0.1.2-1_i386 +libnids-dev_1.23-2_i386 +libnifti-dev_2.0.0-1_i386 +libnih-dbus-dev_1.0.3-4.1_i386 +libnih-dev_1.0.3-4.1_i386 +libnini-cil-dev_1.1.0+dfsg.2-4_all +libnjb-dev_2.2.7~dfsg0-3_i386 +libnl-3-dev_3.2.7-4_i386 +libnl-cli-3-dev_3.2.7-4_i386 +libnl-dev_1.1-7_i386 +libnl-genl-3-dev_3.2.7-4_i386 +libnl-nf-3-dev_3.2.7-4_i386 +libnl-route-3-dev_3.2.7-4_i386 +libnm-glib-dev_0.9.4.0-10_i386 +libnm-glib-vpn-dev_0.9.4.0-10_i386 +libnm-gtk-dev_0.9.4.1-5_i386 +libnm-util-dev_0.9.4.0-10_i386 +libnmz7-dev_2.0.21-6_i386 +libnoise-dev_1.0.0+nmu1_i386 +libnotify-cil-dev_0.4.0~r3032-6_all +libnotify-dev_0.7.5-1_i386 +libnotmuch-dev_0.13.2-1_i386 +libnova-dev_0.14.0-2_i386 +libnpth0-dev_0.90-2_i386 +libnsbmp0-dev_0.0.1-1.1_i386 +libnsgif0-dev_0.0.1-1.1_i386 +libnspr4-dev_2:4.9.2-1+deb7u1_i386 +libnss3-dev_2:3.14.5-1_i386 +libntfs-dev_2.0.0-1+b1_i386 +libntl-dev_5.5.2-2_i386 +libntlm0-dev_1.2-1_i386 +libntrack-dev_016-1.1_i386 +libntrack-glib-dev_016-1.1_i386 +libntrack-gobject-dev_016-1.1_i386 +libntrack-qt4-dev_016-1.1_i386 +libnuclient-dev_2.4.3-2.2_i386 +libnuma-dev_2.0.8~rc4-1_i386 +libnunit-cil-dev_2.6.0.12051+dfsg-2_all +libnussl-dev_2.4.3-2.2_i386 +libnvtt-dev_2.0.8-1+dfsg-2_i386 +libnxcl-dev_0.9-3.1_i386 +libnxml0-dev_0.18.3-4_i386 +libnzb-dev_0.0.20050629-6.1_i386 +liboasis-ocaml-dev_0.2.0-6_i386 +liboasis3-dev_3.3.beta.dfsg.1-8+b1_i386 +liboath-dev_1.12.4-1_i386 +liboauth-dev_0.9.4-3.1_i386 +libobby-0.4-dev_0.4.8-1_i386 +libobexftp0-dev_0.23-1.1_i386 +libobrowser-ocaml-dev_1.1.1+dfsg-1+b9_i386 +libobus-ocaml-dev_1.1.3-1+b8_i386 +libocamlbricks-ocaml-dev_0.50.1-4+b5_i386 +libocamlgraph-ocaml-dev_1.8.2-2_i386 +libocamlgraph-viewer-ocaml-dev_1.8.2-2_i386 +libocamlgsl-ocaml-dev_0.6.0-7+b2_i386 +libocamlnet-gtk2-ocaml-dev_3.5.1-1_i386 +libocamlnet-ocaml-dev_3.5.1-1_i386 +libocamlnet-ssl-ocaml-dev_3.5.1-1_i386 +libocamlodbc-ocaml-dev_2.15-5+b3_i386 +libocamlviz-ocaml-dev_1.01-2+b2_i386 +libocas-dev_0.93-1_i386 +liboce-foundation-dev_0.9.1-3_i386 +liboce-modeling-dev_0.9.1-3_all +liboce-ocaf-dev_0.9.1-3_all +liboce-ocaf-lite-dev_0.9.1-3_all +liboce-visualization-dev_0.9.1-3_all +libocpf-dev_1:1.0-3_i386 +libocrad-dev_0.22~rc1-2_i386 +libocsigen-ocaml-dev_1.3.4-2+b12_i386 +libocsigen-xhtml-ocaml-dev_1.3.4-2+b12_i386 +libocsigenserver-ocaml-dev_2.1-1_i386 +liboctave-dev_3.6.2-5+deb7u1_i386 +libode-dev_2:0.11.1-4_i386 +libode-sp-dev_2:0.11.1-4_i386 +libodin-dev_1.8.5-2_i386 +libodn-ocaml-dev_0.0.8-1_i386 +libofa0-dev_0.9.3-5_i386 +libofapi-dev_0git20070620-6_i386 +libofdt-dev_1.3.6-1_i386 +libofetion-dev_2.2.2-1_i386 +libofx-dev_1:0.9.4-2.1_i386 +libogdi3.2-dev_3.2.0~beta2-7_i386 +libogg-dev_1.3.0-4_i386 +libogg-ocaml-dev_0.4.3-1+b1_i386 +liboggkate-dev_0.4.1-1_i386 +liboggplay1-dev_0.2.1~git20091227-1.2_i386 +liboggz2-dev_1.1.1-1_i386 +liboglappth-dev_1.0.0-2_i386 +libogre-1.8-dev_1.8.0+dfsg1-3_i386 +libogre-dev_1.7.4+dfsg1-7_i386 +liboil0.3-dev_0.3.17-2_i386 +libois-dev_1.3.0+dfsg0-5_i386 +libomhacks-dev_0.16-1_i386 +libomnievents-dev_1:2.6.2-2_i386 +libomniorb4-dev_4.1.6-2_i386 +libomnithread3-dev_4.1.6-2_i386 +libomxil-bellagio-dev_0.9.3-1+b1_i386 +libonig-dev_5.9.1-1_i386 +liboobs-1-dev_3.0.0-1_i386 +liboop-dev_1.0-9_i386 +libooptools-dev_2.7-1_i386 +libopal-dev_3.10.4~dfsg-3_i386 +libopenafs-dev_1.6.1-3+deb7u2_i386 +libopenais-dev_1.1.4-4.1_i386 +libopenal-dev_1:1.14-4_i386 +libopenbabel-dev_2.3.1+dfsg-4_i386 +libopenblas-dev_0.1.1-6+deb7u3_i386 +libopencc-dev_0.3.0-3_i386 +libopenconnect-dev_3.20-4_i386 +libopencore-amrnb-dev_0.1.3-2_i386 +libopencore-amrwb-dev_0.1.3-2_i386 +libopencryptoki-dev_2.3.1+dfsg-3_i386 +libopencsg-dev_1.3.2-2_i386 +libopenct1-dev_0.6.20-1.2_i386 +libopencv-calib3d-dev_2.3.1-11_i386 +libopencv-contrib-dev_2.3.1-11_i386 +libopencv-core-dev_2.3.1-11_i386 +libopencv-dev_2.3.1-11_i386 +libopencv-features2d-dev_2.3.1-11_i386 +libopencv-flann-dev_2.3.1-11_i386 +libopencv-gpu-dev_2.3.1-11_i386 +libopencv-highgui-dev_2.3.1-11_i386 +libopencv-imgproc-dev_2.3.1-11_i386 +libopencv-legacy-dev_2.3.1-11_i386 +libopencv-ml-dev_2.3.1-11_i386 +libopencv-objdetect-dev_2.3.1-11_i386 +libopencv-video-dev_2.3.1-11_i386 +libopendkim-dev_2.6.8-4_i386 +libopenexr-dev_1.6.1-6_i386 +libopenhpi-dev_2.14.1-1.2_i386 +libopenigtlink1-dev_1.9.2~svn7468-1_i386 +libopenimageio-dev_1.0.5+dfsg0-1_i386 +libopenipmi-dev_2.0.16-1.3_i386 +libopenjpeg-dev_1.3+dfsg-4.7_i386 +libopenmeeg-dev_2.0.0.dfsg-5_i386 +libopenmpi-dev_1.4.5-1_i386 +libopenobex1-dev_1.5-2_i386 +libopenr2-dev_1.3.2-1.1_i386 +libopenraw-dev_0.0.9-3+b1_i386 +libopenrawgnome-dev_0.0.9-3+b1_i386 +libopenscap-dev_0.8.0-4+b1_i386 +libopenscenegraph-dev_3.0.1-4_i386 +libopenslide-dev_3.2.6-2_i386 +libopensm2-dev_3.2.6-20090317-2.1_i386 +libopenthreads-dev_3.0.1-4_i386 +libopentk-cil-dev_1.0.20101006+dfsg1-1_all +libopentoken3-dev_4.0b-3_i386 +libopenturns-dev_1.0-4_i386 +libopenusb-dev_1.1.0-2_i386 +libopenvg1-mesa-dev_8.0.5-4+deb7u2_i386 +libopenvrml-dev_0.18.9-5+deb7u1_i386 +libopenwalnut1-dev_1.2.5-1.1+b1_i386 +liboping-dev_1.6.2-1_i386 +libopkele-dev_2.0.4-5.3_i386 +libopts25-dev_1:5.12-0.1_i386 +libopus-dev_0.9.14+20120615-1+nmu1_i386 +liborange-dev_0.4-2_i386 +liborbit2-dev_1:2.14.19-0.1_i386 +liborc-0.4-dev_1:0.4.16-2_i386 +liborigin-dev_20080225-2.1_i386 +liborigin2-dev_2:20110117-1+b2_i386 +libortp-dev_3.5.2-10_i386 +liboscpack-dev_1.0.2-1_i386 +libosgearth-dev_2.0+dfsg-4+b3_i386 +libosinfo-1.0-dev_0.1.1-1_i386 +libosip2-dev_3.6.0-4_i386 +libosl-dev_0.5.0-1_i386 +libosmesa6-dev_8.0.5-4+deb7u2_i386 +libosmgpsmap-dev_0.7.3-3_i386 +libosmium-dev_0.0~20111213-g7f3500a-3+b2_i386 +libosmpbf-dev_1.2.1-3_i386 +libosp-dev_1.5.2-10_i386 +libosptk3-dev_3.4.2-1+b1_i386 +libossim-dev_1.7.21-4_i386 +libossp-sa-dev_1.2.6-1_i386 +libossp-uuid-dev_1.6.2-1.3_i386 +libostyle-dev_1.4devel1-20.1+b1_i386 +libotcl1-dev_1.14+dfsg-2_i386 +libotf-dev_0.9.12-2_i386 +libotf-trace-dev_1.10.2+dfsg-2_i386 +libotpw-dev_1.3-2_i386 +libotr2-dev_3.2.1-1+deb7u1_i386 +libots-dev_0.5.0-2.1_i386 +libounit-ocaml-dev_1.1.1-1_i386 +libow-dev_2.8p15-1_i386 +libowfat-dev_0.28-6_i386 +libowfat-dietlibc-dev_0.28-6_i386 +libownet-dev_2.8p15-1_i386 +libp11-dev_0.2.8-2_i386 +libp11-kit-dev_0.12-3_i386 +libpackagekit-glib2-dev_0.7.6-3_i386 +libpackagekit-qt2-dev_0.7.6-3_i386 +libpacketdump3-dev_3.0.14-1_i386 +libpacklib-lesstif1-dev_20061220+dfsg3-2_i386 +libpacklib1-dev_20061220+dfsg3-2_i386 +libpacparser-dev_1.3.0-2_i386 +libpam-ocaml-dev_1.1-4+b3_i386 +libpam0g-dev_1.1.3-7.1_i386 +libpanel-applet-4-dev_3.4.2.1-4_i386 +libpango1.0-dev_1.30.0-1_i386 +libpangomm-1.4-dev_2.28.4-1_i386 +libpano13-dev_2.9.18+dfsg-5_i386 +libpantomime1.2-dev_1.2.0~pre3+snap20071004+dfsg-4+b1_i386 +libpaper-dev_1.1.24+nmu2_i386 +libpaps-dev_0.6.8-6_i386 +libpaq-dev_1.0.4-3+b1_i386 +libpar2-0-dev_0.2.1-1_i386 +libpari-dev_2.5.1-2_i386 +libparpack2-dev_3.1.1-2.1_i386 +libparrot-dev_4.0.0-3_i386 +libparser++-dev_0.2.3-2_all +libparted0-dev_2.3-12_i386 +libpasswdqc-dev_1.2.0-1_all +libpath-utils-dev_0.1.3-2_i386 +libpathfinder-dev_1.1.3-0.4+b1_i386 +libpawlib-lesstif3-dev_1:2.14.04.dfsg.2-8_i386 +libpawlib2-dev_1:2.14.04.dfsg.2-8_i386 +libpcap-dev_1.3.0-1_all +libpcap0.8-dev_1.3.0-1_i386 +libpcapnav0-dev_0.8-1_i386 +libpci-dev_1:3.1.9-6_i386 +libpciaccess-dev_0.13.1-2_i386 +libpcl1-dev_1.6-1_i386 +libpcre++-dev_0.9.5-5.1_i386 +libpcre-ocaml-dev_6.2.5-1_i386 +libpcre3-dev_1:8.30-5_i386 +libpcscada2-dev_0.7.1-4_i386 +libpcsclite-dev_1.8.4-1+deb7u1_i386 +libpdflib804-2-dev_20061220+dfsg3-2_i386 +libpe-rules2-dev_1.1.7-1_i386 +libpe-status3-dev_1.1.7-1_i386 +libpeas-dev_1.4.0-2_i386 +libpengine3-dev_1.1.7-1_i386 +libperl-dev_5.14.2-21+deb7u1_i386 +libperl4caml-ocaml-dev_0.9.5-4+b4_i386 +libpetsc3.2-dev_3.2.dfsg-6_i386 +libpfqueue-dev_0.5.6-8_i386 +libpfs-dev_1.8.5-1_i386 +libpgm-dev_5.1.118-1~dfsg-0.1_i386 +libpgocaml-ocaml-dev_1.5-2_i386 +libpgpool-dev_3.1.3-5_i386 +libpgtcl-dev_1:1.5-6_i386 +libphash0-dev_0.9.4-1.2_i386 +libphat-dev_0.4.1-5_i386 +libphobos-4.4-dev_1.063-4.4.7-1_i386 +libphobos2-4.6-dev_0.29.1-4.6.3-2_i386 +libphone-ui-dev_1:0.0.1+git20110825-3_i386 +libphone-utils-dev_0.1+git20110523-2.1_i386 +libphonon-dev_4:4.6.0.0-3_i386 +libphononexperimental-dev_4:4.6.0.0-3_i386 +libphotos202-dev_20061220+dfsg3-2_i386 +libphtools2-dev_20061220+dfsg3-2_i386 +libphysfs-dev_2.0.2-6_i386 +libpiano-dev_2012.05.06-2_i386 +libpigment0.3-dev_0.3.17-1_i386 +libpils2-dev_1.0.9+hg2665-1_i386 +libpinyin0-dev_0.6.91-1_i386 +libpion-common-dev_4.0.7+dfsg-3.1_i386 +libpion-net-dev_4.0.7+dfsg-3.1_i386 +libpipeline-dev_1.2.1-1_i386 +libpisock-dev_0.12.5-5_i386 +libpixman-1-dev_0.26.0-4+deb7u1_i386 +libpkcs11-helper1-dev_1.09-1_i386 +libplayer-dev_2.0.1-2.1_i386 +libplayerc++3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerc3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercommon3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercore3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerdrivers3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerinterface3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerjpeg3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayertcp3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerwkb3.0-dev_3.0.2+dfsg-4+b1_i386 +libplib-dev_1.8.5-6_i386 +libplist++-dev_1.8-1_i386 +libplist-dev_1.8-1_i386 +libpload-dev_1.4.2-3_i386 +libplot-dev_2.6-3_i386 +libploticus0-dev_2.41-5_i386 +libplotmm-dev_0.1.2-2_i386 +libplplot-ada0-dev_5.9.9-5_i386 +libplplot-dev_5.9.9-5_i386 +libplumb2-dev_1.0.9+hg2665-1_i386 +libplumbgpl2-dev_1.0.9+hg2665-1_i386 +libpmap3.0-dev_3.0.2+dfsg-4+b1_i386 +libpmi0-dev_2.3.4-2+b1_i386 +libpmount-dev_0.0.16_i386 +libpng++-dev_0.2.5-1_all +libpng12-dev_1.2.49-1_i386 +libpnglite-dev_0.1.17-1_i386 +libpoco-dev_1.3.6p1-4_i386 +libpodofo-dev_0.9.0-1.1+b1_i386 +libpoker-eval-dev_138.0-1_i386 +libpolarssl-dev_1.2.9-1~deb7u2_i386 +libpoldiff-dev_3.3.7-3_i386 +libpolkit-agent-1-dev_0.105-3_i386 +libpolkit-backend-1-dev_0.105-3_i386 +libpolkit-gobject-1-dev_0.105-3_i386 +libpolkit-qt-1-dev_0.103.0-1_i386 +libpolybori-dev_0.5~rc1-2.2_i386 +libpolylib64-dev_5.22.5-3+dfsg_i386 +libpolyml-dev_5.2.1-1.1_i386 +libpolyorb2-dev_2.8~20110207-5.1_i386 +libpomp-dev_1.1+dfsg-2_i386 +libpoppler-cil-dev_0.0.3-2_all +libpoppler-cpp-dev_0.18.4-6_i386 +libpoppler-dev_0.18.4-6_i386 +libpoppler-glib-dev_0.18.4-6_i386 +libpoppler-private-dev_0.18.4-6_i386 +libpoppler-qt4-dev_0.18.4-6_i386 +libpopplerkit-dev_0.0.20051227svn-7+b1_i386 +libpopt-dev_1.16-7_i386 +libportaudio-dev_18.1-7.1_i386 +libportaudio-ocaml-dev_0.2.0-1+b1_i386 +libportmidi-dev_1:184-2.1_i386 +libportsmf-dev_0.1~svn20101010-3_i386 +libpostgresql-ocaml-dev_1.18.0-1_i386 +libpostproc-dev_6:0.8.10-1_i386 +libpotrace-dev_1.10-1_i386 +libpowerman0-dev_2.3.5-1_i386 +libppd-dev_2:0.10-7.1_i386 +libppl0.11-dev_0.11.2-8_i386 +libpq-dev_9.1.13-0wheezy1_i386 +libpqxx3-dev_3.1-1.1_i386 +libprelude-dev_1.0.0-9_i386 +libpreludedb-dev_1.0.0-1.1+b2_i386 +libpresage-dev_0.8.8-1_i386 +libpri-dev_1.4.12-2_i386 +libprinterconf-dev_0.5-12_i386 +libprintsys-dev_0.6-13_i386 +libprison-dev_1.0+dfsg-1_i386 +libprocps0-dev_1:3.3.3-3_i386 +libproj-dev_4.7.0-2_i386 +libprojectm-dev_2.1.0+dfsg-1_i386 +libprojectm-qt-dev_2.1.0+dfsg-1_i386 +libprotobuf-c0-dev_0.14-1+b1_i386 +libprotobuf-dev_2.4.1-3_i386 +libprotoc-dev_2.4.1-3_i386 +libproxy-dev_0.3.1-6_i386 +libproxychains-dev_3.1-3_i386 +libpspell-dev_0.60.7~20110707-1_i386 +libpst-dev_0.6.54-4.1_i386 +libpstoedit-dev_3.60-2+b1_i386 +libpstreams-dev_0.7.0-2_all +libpt-dev_2.10.4~dfsg-1_i386 +libptexenc-dev_2012.20120628-4_i386 +libpth-dev_2.0.7-16_i386 +libpthread-stubs0-dev_0.3-3_i386 +libpthread-workqueue-dev_0.8.2-1_i386 +libptscotch-dev_5.1.12b.dfsg-1.2_i386 +libpugl-dev_0~svn32+dfsg0-1_i386 +libpulse-dev_2.0-6.1_i386 +libpulse-ocaml-dev_0.1.2-1+b1_i386 +libpuma-dev_1:1.1+svn20120529-2_i386 +libpurelibc-dev_0.4.1-1_i386 +libpurple-dev_2.10.9-1~deb7u1_all +libpuzzle-dev_0.9-5_i386 +libpwl-dev_0.11.2-8_i386 +libpxp-ocaml-dev_1.2.2-1+b4_i386 +libpycaml-ocaml-dev_0.82-14+b2_i386 +libpyside-dev_1.1.1-3_i386 +libpythia8-dev_8.1.65-1_i386 +libpythonqt2-dev_2.0.1-1.1_i386 +libqalculate-dev_0.9.7-8_i386 +libqapt-dev_1.3.0-2_i386 +libqb-dev_0.11.1-2_i386 +libqca2-dev_2.0.3-4_i386 +libqd-dev_2.3.11.dfsg-2.1_i386 +libqdaccolib-dev_0.8.2-1_i386 +libqdbm++-dev_1.8.78-2_i386 +libqdbm-dev_1.8.78-2_i386 +libqdjango-dev_0.2.5-2_i386 +libqedje-dev_0.4.0+lgpl-3_i386 +libqfits-dev_6.2.0-5_i386 +libqgis-dev_1.7.4+1.7.5~20120320-1.1+b1_i386 +libqglviewer-qt4-dev_2.3.4-4.2_i386 +libqgpsmm-dev_3.6-4+deb7u1_i386 +libqhull-dev_2009.1-3_i386 +libqimageblitz-dev_1:0.0.6-4_i386 +libqjson-dev_0.7.1-7_i386 +libqmf-dev_0.16-6+deb7u1_i386 +libqmf2-dev_0.16-6+deb7u1_i386 +libqmfconsole2-dev_0.16-6+deb7u1_i386 +libqmfengine1-dev_0.16-6+deb7u1_i386 +libqmmp-dev_0.5.5-1+b1_i386 +libqmmpui-dev_0.5.5-1+b1_i386 +libqoauth-dev_1.0.1-1_i386 +libqof-dev_0.8.6-1_i386 +libqofexpensesobjects-dev_0.1.9-2_i386 +libqpdf-dev_2.3.1-4_i386 +libqpidbroker2-dev_0.16-6+deb7u1_i386 +libqpidclient2-dev_0.16-6+deb7u1_i386 +libqpidcommon2-dev_0.16-6+deb7u1_i386 +libqpidmessaging2-dev_0.16-6+deb7u1_i386 +libqpidtypes1-dev_0.16-6+deb7u1_i386 +libqpol-dev_3.3.7-3_i386 +libqpx-dev_0.7.1.002-5_i386 +libqrencode-dev_3.3.0-2_i386 +libqrupdate-dev_1.1.1-1_i386 +libqsastime-dev_5.9.9-5_i386 +libqscintilla2-dev_2.6.2-2_all +libqt4-dev_4:4.8.2+dfsg-11_i386 +libqt4-opengl-dev_4:4.8.2+dfsg-11_i386 +libqt4-private-dev_4:4.8.2+dfsg-11_i386 +libqt4pas-dev_2.5-6_i386 +libqtassistantclient-dev_4.6.3-4_i386 +libqtexengine-dev_0.3-3_i386 +libqtgstreamer-dev_0.10.2-2_i386 +libqtruby4shared-dev_4:4.8.4-1_i386 +libqtwebkit-dev_2.2.1-5_i386 +libquantlib0-dev_1.2-2+b1_i386 +libquantum-dev_1.1.0-3_i386 +libquicktime-dev_2:1.2.4-3_i386 +libquorum-dev_1.4.2-3_i386 +libquvi-dev_0.4.1-1_i386 +libqwt-dev_6.0.0-1.2_i386 +libqwt5-qt4-dev_5.2.2-3_i386 +libqwtplot3d-qt4-dev_0.2.7+svn191-7_i386 +libqxmlrpc-dev_0.0.svn6-2_i386 +libqxmpp-dev_0.4.92-1_i386 +libqxt-dev_0.6.1-6_i386 +libqzeitgeist-dev_0.7.0-1+b1_i386 +libqzion-dev_0.4.0+lgpl-4_i386 +librabbitmq-dev_0.0.1.hg216-1_i386 +libradare2-dev_0.9-3_i386 +libradius1-dev_0.3.2-14_i386 +libradiusclient-ng-dev_0.5.6-1.1_i386 +libranlip-dev_1.0-4.1_i386 +librapi2-dev_0.15-2.1_i386 +libraptor1-dev_1.4.21-7.1_i386 +libraptor2-dev_2.0.8-2_i386 +librarian-dev_0.8.1-5_i386 +librasqal3-dev_0.9.29-1_i386 +librasterlite-dev_1.1~svn11-2_i386 +libraul-dev_0.8.0+dfsg0-0.1+b1_i386 +libraw-dev_0.14.6-2_i386 +libraw1394-dev_2.0.9-1_i386 +librcc-dev_0.2.9-3_i386 +librcd-dev_0.1.13-3_i386 +librdf0-dev_1.0.15-1+b1_i386 +librdkit-dev_201203-3_i386 +librdmacm-dev_1.0.15-1+deb7u1_i386 +librdmawrap2-dev_0.16-6+deb7u1_i386 +libreact-ocaml-dev_0.9.3-1_i386 +libreadline-dev_6.2+dfsg-0.1_i386 +libreadline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +libreadline6-dev_6.2+dfsg-0.1_i386 +librec-dev_1.5-1_i386 +librecode-dev_3.6-20_i386 +libref-array-dev_0.1.3-2_i386 +libregina3-dev_3.6-2_i386 +libregistry-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libreins-ocaml-dev_0.1a-4+b1_i386 +libreiser4-dev_1.0.7-6.3_i386 +librelp-dev_1.0.0-1_i386 +libremctl-dev_3.2-4_i386 +librenaissance0-dev_0.9.0-4+b3_i386 +libreoffice-dev_1:3.5.4+dfsg2-0+deb7u2_i386 +librep-dev_0.90.2-1.3_i386 +libreplaygain-dev_1.0~r475-1_i386 +libres-ocaml-dev_3.2.0-2+b3_i386 +libresample1-dev_0.1.3-4_i386 +libresid-builder-dev_2.1.1-14_i386 +libresiprocate-1.8-dev_1.8.5-4_i386 +libresiprocate-turn-client-1.8-dev_1.8.5-4_i386 +librest-dev_0.7.12-3_i386 +librest-extras-dev_0.7.12-3_i386 +librhash-cil-dev_1.2.9-8+deb7u1_all +librhash-dev_1.2.9-8+deb7u1_i386 +librheolef-dev_6.1-2.1_i386 +librivet-dev_1.8.0-1_i386 +librlog-dev_1.4-2_i386 +libroar-dev_1.0~beta2-3_i386 +libroot-bindings-python-dev_5.34.00-2_i386 +libroot-bindings-ruby-dev_5.34.00-2_i386 +libroot-core-dev_5.34.00-2_i386 +libroot-geom-dev_5.34.00-2_i386 +libroot-graf2d-gpad-dev_5.34.00-2_i386 +libroot-graf2d-graf-dev_5.34.00-2_i386 +libroot-graf2d-postscript-dev_5.34.00-2_i386 +libroot-graf3d-eve-dev_5.34.00-2_i386 +libroot-graf3d-g3d-dev_5.34.00-2_i386 +libroot-graf3d-gl-dev_5.34.00-2_i386 +libroot-gui-dev_5.34.00-2_i386 +libroot-gui-ged-dev_5.34.00-2_i386 +libroot-hist-dev_5.34.00-2_i386 +libroot-hist-spectrum-dev_5.34.00-2_i386 +libroot-html-dev_5.34.00-2_i386 +libroot-io-dev_5.34.00-2_i386 +libroot-io-xmlparser-dev_5.34.00-2_i386 +libroot-math-foam-dev_5.34.00-2_i386 +libroot-math-genvector-dev_5.34.00-2_i386 +libroot-math-mathcore-dev_5.34.00-2_i386 +libroot-math-mathmore-dev_5.34.00-2_i386 +libroot-math-matrix-dev_5.34.00-2_i386 +libroot-math-minuit-dev_5.34.00-2_i386 +libroot-math-mlp-dev_5.34.00-2_i386 +libroot-math-physics-dev_5.34.00-2_i386 +libroot-math-quadp-dev_5.34.00-2_i386 +libroot-math-smatrix-dev_5.34.00-2_i386 +libroot-math-splot-dev_5.34.00-2_i386 +libroot-math-unuran-dev_5.34.00-2_i386 +libroot-misc-memstat-dev_5.34.00-2_i386 +libroot-misc-minicern-dev_5.34.00-2_i386 +libroot-misc-table-dev_5.34.00-2_i386 +libroot-montecarlo-eg-dev_5.34.00-2_i386 +libroot-montecarlo-vmc-dev_5.34.00-2_i386 +libroot-net-auth-dev_5.34.00-2_i386 +libroot-net-bonjour-dev_5.34.00-2_i386 +libroot-net-dev_5.34.00-2_i386 +libroot-net-ldap-dev_5.34.00-2_i386 +libroot-proof-clarens-dev_5.34.00-2_i386 +libroot-proof-dev_5.34.00-2_i386 +libroot-proof-proofplayer-dev_5.34.00-2_i386 +libroot-roofit-dev_5.34.00-2_i386 +libroot-tmva-dev_5.34.00-2_i386 +libroot-tree-dev_5.34.00-2_i386 +libroot-tree-treeplayer-dev_5.34.00-2_i386 +librostlab-blast0-dev_1.0.0-2_i386 +librostlab3-dev_1.0.20-1_i386 +librpcsecgss-dev_0.19-5_i386 +librplay3-dev_3.3.2-14_i386 +librpm-dev_4.10.0-5+deb7u1_i386 +librra-dev_0.14-1.2_i386 +librrd-dev_1.4.7-2_i386 +librsl-dev_1.42-2_i386 +librsskit-dev_0.3-2_i386 +librsvg2-2.0-cil-dev_2.26.0-8_all +librsvg2-dev_2.36.1-2_i386 +librsync-dev_0.9.7-9_i386 +librtai-dev_3.8.1-4_i386 +librtas-dev_1.3.6-1_i386 +librtasevent-dev_1.3.6-1_i386 +librtaudio-dev_4.0.10~ds0-2_i386 +librtfcomp-dev_1.1-5+b1_i386 +librtfilter-dev_1.1-4_i386 +librtmidi-dev_1.0.15~ds0-2_i386 +librtmp-dev_2.4+20111222.git4e06e21-1_i386 +librubberband-dev_1.3-1.3_i386 +librudecgi-dev_5.0.0-1_i386 +libruli4-dev_0.33-1.1_i386 +librxp-dev_1.5.0-1_i386 +libs3-dev_2.0-1_i386 +libs3d-dev_0.2.2-8_i386 +libs3dw-dev_0.2.2-8_i386 +libsaamf3-dev_1.1.4-4.1_i386 +libsackpt3-dev_1.1.4-4.1_i386 +libsaclm3-dev_1.1.4-4.1_i386 +libsaevt3-dev_1.1.4-4.1_i386 +libsage-dev_0.2.0-4.1_i386 +libsalck3-dev_1.1.4-4.1_i386 +libsam-dev_1.4.2-3_i386 +libsamba-credentials-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-hostconfig-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-policy-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-util-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamdb-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsaml2-dev_2.4.3-4_i386 +libsampleicc-dev_1.6.4-1+b1_i386 +libsamplerate-ocaml-dev_0.1.1-1+b3_i386 +libsamplerate0-dev_0.1.8-5_i386 +libsamsg4-dev_1.1.4-4.1_i386 +libsane-dev_1.0.22-7.4_i386 +libsane-extras-dev_1.0.22.2_i386 +libsanlock-dev_2.2-2_i386 +libsary-dev_1:1.2.0-2.1_i386 +libsasl2-dev_2.1.25.dfsg1-6+deb7u1_i386 +libsatmr3-dev_1.1.4-4.1_i386 +libsbjson-dev_2.3.2-2_i386 +libsbsms-dev_2.0.1-1_i386 +libsbuf-dev_9.0+ds1-4_i386 +libsbuild-dev_1.6.4-4_i386 +libsc-dev_2.3.1-14_i386 +libscalapack-mpi-dev_1.8.0-9_i386 +libscalapack-pvm-dev_1.8.0-9_i386 +libscalc-dev_0.2.4-1_i386 +libscamperfile0-dev_20111202b-1_i386 +libschroedinger-dev_1.0.11-2_i386 +libschroedinger-ocaml-dev_0.1.0-1+b3_i386 +libscim-dev_1.4.13-5_i386 +libscm-dev_5e5-3.2_i386 +libscotch-dev_5.1.12b.dfsg-1.2_i386 +libscotchmetis-dev_5.1.12b.dfsg-1.2_i386 +libscotchparmetis-dev_5.1.12b.dfsg-1.2_i386 +libsctp-dev_1.0.11+dfsg-2_i386 +libscythestat-dev_1.0.2-1_all +libsdl-console-dev_2.1-3_i386 +libsdl-gfx1.2-dev_2.0.23-3_i386 +libsdl-image1.2-dev_1.2.12-2_i386 +libsdl-mixer1.2-dev_1.2.12-3_i386 +libsdl-net1.2-dev_1.2.8-2_i386 +libsdl-ocaml-dev_0.9.0-1_i386 +libsdl-pango-dev_0.1.2-6_i386 +libsdl-sge-dev_030809dfsg-3_i386 +libsdl-sound1.2-dev_1.0.3-6_i386 +libsdl-stretch-dev_0.3.1-3_i386 +libsdl-ttf2.0-dev_2.0.11-2_i386 +libsdl1.2-dev_1.2.15-5_i386 +libsdpa-dev_7.3.8+dfsg-1_i386 +libsearchclient-dev_0.7.7-3_i386 +libseaudit-dev_3.3.7-3_i386 +libseed-gtk3-dev_3.2.0-2_i386 +libsefs-dev_3.3.7-3_i386 +libselinux1-dev_2.1.9-5_i386 +libsemanage1-dev_2.1.6-6_i386 +libsensors-applet-plugin-dev_3.0.0-0.2_i386 +libsensors4-dev_1:3.3.2-2+deb7u1_i386 +libsepol1-dev_2.1.4-3_i386 +libserd-dev_0.14.0~dfsg0-2_i386 +libserf-dev_1.1.0-2_i386 +libsexplib-camlp4-dev_7.0.4-2_i386 +libsexy-dev_0.1.11-2+b1_i386 +libsfml-dev_1.6+dfsg2-2_i386 +libsfst1-1.2-0-dev_1.2.0-1.2_i386 +libsgutils2-dev_1.33-1_i386 +libsha-ocaml-dev_1.7-2+b2_i386 +libshairport-dev_1.2.1~git20120110.aeb4987-2_i386 +libshevek-dev_1.3-1_i386 +libshhmsg1-dev_1.4.1-4.1_i386 +libshhopt1-dev_1.1.7-2.1_i386 +libshiboken-dev_1.1.1-1_i386 +libshibsp-dev_2.4.3+dfsg-5+b1_i386 +libshisa-dev_1.0.1-2_i386 +libshishi-dev_1.0.1-2_i386 +libshout-ocaml-dev_0.2.7-1+b3_i386 +libshout3-dev_2.2.2-8_i386 +libshp-dev_1.2.10-7_i386 +libshr-glib-dev_2011.03.08.2~git20110930-2_i386 +libsidl-dev_1.4.0.dfsg-8.1_i386 +libsidplay1-dev_1.36.59-5_i386 +libsidplay2-dev_2.1.1-14_i386 +libsidplayfp-dev_0.3.5-1_i386 +libsidutils-dev_2.1.1-14_i386 +libsieve2-dev_2.2.6-1.1_i386 +libsigc++-1.2-dev_1.2.7-2_i386 +libsigc++-2.0-dev_2.2.10-0.2_i386 +libsigc++-dev_1.0.4-9.4_i386 +libsigrok0-dev_0.1.0-2_i386 +libsigrokdecode0-dev_0.1.0-2_i386 +libsigsegv-dev_2.9-4_i386 +libsilly-dev_0.1.0-3_i386 +libsilo-dev_4.8-13_i386 +libsimage-dev_1.7.0-1.1+b1_i386 +libsimplelist0-dev_0.3.4-2_i386 +libsipwitch-dev_1.2.4-1_i386 +libsiscone-dev_2.0.5-1_i386 +libsiscone-spherical-dev_2.0.5-1_i386 +libskk-dev_0.0.12-3_i386 +libskstream-0.3-dev_0.3.8-1_i386 +libslang2-dev_2.2.4-15_i386 +libslepc3.2-dev_3.2-p5-1_i386 +libslp-dev_1.2.1-9_i386 +libslurm-dev_2.3.4-2+b1_i386 +libslurmdb-dev_2.3.4-2+b1_i386 +libslv2-dev_0.6.6+dfsg1-2_i386 +libsm-dev_2:1.2.1-2_i386 +libsmbclient-dev_2:3.6.6-6+deb7u3_i386 +libsmbclient-raw-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsmbios-dev_2.0.3.dfsg-1.1_i386 +libsmf-dev_1.3-2_i386 +libsmi2-dev_0.4.8+dfsg2-7_i386 +libsmokekde-dev_4:4.8.4-1_i386 +libsmokeqt4-dev_4:4.8.4-1_i386 +libsmpeg-dev_0.4.5+cvs20030824-5_i386 +libsnacc-dev_1.3.1-1_i386 +libsnack2-dev_2.2.10-dfsg1-12.1_i386 +libsnappy-dev_1.0.5-2_i386 +libsndfile1-dev_1.0.25-5_i386 +libsndobj-dev_2.6.6.1-3_i386 +libsnmp-dev_5.4.3~dfsg-2.8_i386 +libsnmpkit-dev_0.9-16_i386 +libsocialweb-client-dev_0.25.20-2.1_i386 +libsocialweb-dev_0.25.20-2.1_i386 +libsocksd0-dev_1.1.19.dfsg-3+b3_i386 +libsofa-c-dev_2012.03.01-1_i386 +libsofa1-dev_1.0~beta4-7_i386 +libsofia-sip-ua-dev_1.12.11+20110422-1_i386 +libsofia-sip-ua-glib-dev_1.12.11+20110422-1_i386 +libsofthsm-dev_1.3.3-2_i386 +libsoil-dev_1.07~20080707.dfsg-2_i386 +libsombok-dev_2.2.1-1_i386 +libsonic-dev_0.1.17-1.1_i386 +libsope-dev_1.3.16-1_i386 +libsoprano-dev_2.7.6+dfsg.1-2wheezy1_i386 +libsoqt4-dev_1.5.0-2_i386 +libsord-dev_0.8.0~dfsg0-1_i386 +libsoundgen-dev_0.6-4_i386 +libsoundtouch-dev_1.6.0-3_i386 +libsoundtouch-ocaml-dev_0.1.7-1+b1_i386 +libsoup-gnome2.4-dev_2.38.1-3_i386 +libsoup2.4-dev_2.38.1-3_i386 +libsoupcutter-dev_1.1.7-1.2_i386 +libsource-highlight-dev_3.1.6-1.1_i386 +libsox-dev_14.4.0-3_i386 +libsp-gxmlcpp-dev_1.0.20040603-5_i386 +libsp1-dev_1.3.4-1.2.1-47.1+b1_i386 +libspandsp-dev_0.0.6~pre20-3.1_i386 +libsparsehash-dev_1.10-1_all +libsparskit-dev_2.0.0-2_i386 +libspatialindex-dev_1.7.0-1_i386 +libspatialite-dev_3.0.0~beta20110817-3+deb7u1_i386 +libspctag-dev_0.2-1_i386 +libspectre-dev_0.2.7-2_i386 +libspectrum-dev_1.0.0-3_i386 +libspeechd-dev_0.7.1-6.2_i386 +libspeex-dev_1.2~rc1-7_i386 +libspeex-ocaml-dev_0.2.0-1+b3_i386 +libspeexdsp-dev_1.2~rc1-7_i386 +libspf2-dev_1.2.9-7_i386 +libsphere-dev_3.2-4_i386 +libspice-client-glib-2.0-dev_0.12-5_i386 +libspice-client-gtk-2.0-dev_0.12-5_i386 +libspice-client-gtk-3.0-dev_0.12-5_i386 +libspice-protocol-dev_0.10.1-1_all +libspice-server-dev_0.11.0-1+deb7u1_i386 +libspiro-dev_20071029-2_i386 +libspnav-dev_0.2.2-1_i386 +libspooles-dev_2.2-9_i386 +libsprng2-dev_2.0a-8_i386 +libsqlexpr-ocaml-dev_0.4.1-1+b5_i386 +libsqlheavy-dev_0.1.1-1_i386 +libsqlheavygtk-dev_0.1.1-1_i386 +libsqlite0-dev_2.8.17-7_i386 +libsqlite3-dev_3.7.13-1+deb7u1_i386 +libsqlite3-ocaml-dev_1.6.1-1+b1_i386 +libsquizz-dev_0.99a-2_i386 +libsratom-dev_0.2.0~dfsg0-1_i386 +libsrecord-dev_1.58-1+b1_i386 +libsrf-dev_0.1+dfsg-1_i386 +libsrtp0-dev_1.4.4+20100615~dfsg-2+deb7u1_i386 +libss7-dev_1.0.2-3_i386 +libsscm-dev_0.8.5-2.1_i386 +libssh-dev_0.5.4-1+deb7u1_i386 +libssh2-1-dev_1.4.2-1.1_i386 +libssl-dev_1.0.1e-2+deb7u7_i386 +libssl-ocaml-dev_0.4.6-1_i386 +libsslcommon2-dev_0.16-6+deb7u1_i386 +libssreflect-ocaml-dev_1.3pl4-1_i386 +libsss-sudo-dev_1.8.4-2_i386 +libst-dev_1.9-3_i386 +libstaden-read-dev_1.12.4-1_i386 +libstarlink-ast-dev_7.0.4+dfsg-1_i386 +libstarlink-pal-dev_0.1.0-1_i386 +libstarpu-dev_1.0.1+dfsg-1_i386 +libstartup-notification0-dev_0.12-1_i386 +libstatgrab-dev_0.17-1_i386 +libstdc++6-4.4-dev_4.4.7-2_i386 +libstdc++6-4.6-dev_4.6.3-14_i386 +libstdc++6-4.7-dev_4.7.2-5_i386 +libstemmer-dev_0+svn546-2_i386 +libsteptalk-dev_0.10.0-5+b1_i386 +libstfl-dev_0.22-1+b1_i386 +libstk0-dev_4.4.3-2_i386 +libstlport4.6-dev_4.6.2-7_i386 +libstonith1-dev_1.0.9+hg2665-1_i386 +libstonithd1-dev_1.1.7-1_i386 +libstreamanalyzer-dev_0.7.7-3_i386 +libstreams-dev_0.7.7-3_i386 +libstrigihtmlgui-dev_0.7.7-3_i386 +libstrigiqtdbusclient-dev_0.7.7-3_i386 +libstroke0-dev_0.5.1-6_i386 +libstxxl-dev_1.3.1-4_i386 +libsublime-dev_1.3.1-2_i386 +libsubtitleeditor-dev_0.33.0-1_i386 +libsubunit-dev_0.0.8+bzr176-1_i386 +libsugarext-dev_0.96.1-2_i386 +libsuil-dev_0.6.4~dfsg0-3_i386 +libsuitesparse-dev_1:3.4.0-3_i386 +libsundials-serial-dev_2.5.0-3_i386 +libsunpinyin-dev_2.0.3+git20120607-1_i386 +libsuperlu3-dev_3.0+20070106-3_i386 +libsvga1-dev_1:1.4.3-33_i386 +libsvm-dev_3.12-1_i386 +libsvn-dev_1.6.17dfsg-4+deb7u6_i386 +libsvncpp-dev_0.12.0dfsg-6_i386 +libsvnqt-dev_1.5.5-4.1_i386 +libsvrcore-dev_1:4.0.4-15_i386 +libswami-dev_2.0.0+svn389-2_i386 +libswe-dev_1.77.00.0005-2_i386 +libswiften-dev_2.0~beta1+dev47-1_i386 +libsword-dev_1.6.2+dfsg-5_i386 +libswscale-dev_6:0.8.10-1_i386 +libsx-dev_2.05-3_i386 +libsyfi1.0-dev_1.0.0.dfsg-1_i386 +libsylph-dev_1.1.0-8_i386 +libsymmetrica-dev_2.0-1_i386 +libsynce0-dev_0.15-1.1_i386 +libsyncml-dev_0.5.4-2.1_i386 +libsynfig-dev_0.63.05-1_i386 +libsynopsis0.12-dev_0.12-8_i386 +libsynthesis-dev_3.4.0.16.7-1_i386 +libsysactivity-dev_0.6.4-1_i386 +libsysfs-dev_2.1.0+repack-2_i386 +libsyslog-ng-dev_3.3.5-4_i386 +libsyslog-ocaml-dev_1.4-6+b2_i386 +libsystemd-daemon-dev_44-11+deb7u4_i386 +libsystemd-id128-dev_44-11+deb7u4_i386 +libsystemd-journal-dev_44-11+deb7u4_i386 +libsystemd-login-dev_44-11+deb7u4_i386 +libt1-dev_5.1.2-3.6_i386 +libtacacs+1-dev_4.0.4.19-11_all +libtachyon-dev_0.99~b2+dfsg-0.4_i386 +libtag-extras-dev_1.0.1-3_i386 +libtag1-dev_1.7.2-1_i386 +libtagc0-dev_1.7.2-1_i386 +libtagcoll2-dev_2.0.13-1.1_i386 +libtaglib-cil-dev_2.0.4.0-1_all +libtaglib-ocaml-dev_0.2.0-1+b1_i386 +libtaktuk-1-dev_3.7.4-1_i386 +libtalloc-dev_2.0.7+git20120207-1_i386 +libtamuanova-dev_0.2-2_i386 +libtango7-dev_7.2.6+dfsg-14_i386 +libtaningia-dev_0.2.2-1_i386 +libtaoframework-devil-cil-dev_2.1.svn20090801-9_all +libtaoframework-ffmpeg-cil-dev_2.1.svn20090801-9_all +libtaoframework-freeglut-cil-dev_2.1.svn20090801-9_all +libtaoframework-freetype-cil-dev_2.1.svn20090801-9_all +libtaoframework-ftgl-cil-dev_2.1.svn20090801-9_all +libtaoframework-lua-cil-dev_2.1.svn20090801-9_all +libtaoframework-ode-cil-dev_2.1.svn20090801-9_all +libtaoframework-openal-cil-dev_2.1.svn20090801-9_all +libtaoframework-opengl-cil-dev_2.1.svn20090801-9_all +libtaoframework-physfs-cil-dev_2.1.svn20090801-9_all +libtaoframework-sdl-cil-dev_2.1.svn20090801-9_all +libtar-dev_1.2.16-1+deb7u2_i386 +libtarantool-dev_1.4.6+20120629+2158-1_i386 +libtasn1-3-dev_2.13-2_i386 +libtbb-dev_4.0+r233-1_i386 +libtcc-dev_0.9.26~git20120612.ad5f375-6_i386 +libtcd-dev_2.2.2-1_i386 +libtclap-dev_1.2.1-1_i386 +libtclcl1-dev_1.20-6_i386 +libtdb-dev_1.2.10-2_i386 +libtecla1-dev_1.6.1-5_i386 +libteem-dev_1.11.0~svn5226-1_i386 +libtelepathy-farstream-dev_0.4.0-3_i386 +libtelepathy-glib-dev_0.18.2-2_i386 +libtelepathy-logger-dev_0.4.0-1_i386 +libtelepathy-logger-qt4-dev_0.4.0-1_i386 +libtelepathy-qt4-dev_0.9.1-4_i386 +libtelnet-dev_0.21-1_i386 +libtemplates-parser11.6-dev_11.6-2_i386 +libterralib-dev_4.0.0-4_i386 +libtesseract-dev_3.02.01-6_i386 +libtevent-dev_0.9.16-1_i386 +libtext-ocaml-dev_0.5-1+b2_i386 +libtextwrap-dev_0.1-13_i386 +libthai-dev_0.1.18-2_i386 +libtheora-dev_1.1.1+dfsg.1-3.1_i386 +libtheora-ocaml-dev_0.3.0-1+b3_i386 +libthepeg-dev_1.8.0-1_i386 +libthrust-dev_1.6.0-1_all +libthunar-vfs-1-dev_1.2.0-3+b1_i386 +libthunarx-2-dev_1.2.3-4+b1_i386 +libticables-dev_1.2.0-2_i386 +libticalcs-dev_1.1.3+dfsg1-1_i386 +libticonv-dev_1.1.0-1.1_i386 +libtidy-dev_20091223cvs-1.2_i386 +libtiff4-dev_3.9.6-11_i386 +libtiff5-alt-dev_4.0.2-6+deb7u2_i386 +libtiff5-dev_4.0.2-6+deb7u2_i386 +libtifiles-dev_1.1.1-1_i386 +libtimbl3-dev_6.4.2-1_i386 +libtimblserver2-dev_1.4-2_i386 +libtinfo-dev_5.9-10_i386 +libtinyxml-dev_2.6.2-1_i386 +libtinyxml2-dev_0~git20120518.1.a2ae54e-1_i386 +libtirpc-dev_0.2.2-5_i386 +libtk-img-dev_1:1.3-release-12_i386 +libtnt-dev_1.2.6-1_all +libtntdb-dev_1.2-2+b1_i386 +libtntnet-dev_2.1-2+deb7u1_i386 +libtododb-dev_0.11-3_i386 +libtogl-dev_1.7-12_all +libtokyocabinet-dev_1.4.47-2_i386 +libtokyotyrant-dev_1.1.40-4.1+b1_i386 +libtolua++5.1-dev_1.0.93-3_i386 +libtolua-dev_5.2.0-1_i386 +libtomcrypt-dev_1.17-3.2_i386 +libtommath-dev_0.42.0-1_i386 +libtomoe-dev_0.6.0-1.3_i386 +libtonezone-dev_1:2.5.0.1-2_i386 +libtophide-ocaml-dev_1.0.0-3_all +libtorch3-dev_3.1-2.1_i386 +libtorque2-dev_2.4.16+dfsg-1+deb7u2_i386 +libtorrent-dev_0.13.2-1_i386 +libtorrent-rasterbar-dev_0.15.10-1+b1_i386 +libtorture-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libtotem-dev_3.0.1-8_i386 +libtotem-pg-dev_1.4.2-3_i386 +libtotem-plparser-dev_3.4.2-1_i386 +libtowitoko-dev_2.0.7-8.3_i386 +libtpl-dev_1.5-2_all +libtpm-unseal-dev_1.3.7-1_i386 +libtrace3-dev_3.0.14-1_i386 +libtracker-extract-0.14-dev_0.14.1-3_i386 +libtracker-miner-0.14-dev_0.14.1-3_i386 +libtracker-sparql-0.14-dev_0.14.1-3_i386 +libtransitioner1-dev_1.1.7-1_i386 +libtre-dev_0.8.0-3_i386 +libtreil-dev_1.8-1.1_i386 +libts-dev_1.0-11_i386 +libtse3-dev_0.3.1-4.3_i386 +libtsk-dev_3.2.3-2_i386 +libtspi-dev_0.3.9-3+wheezy1_i386 +libtulip-dev_3.7.0dfsg-4_i386 +libtumbler-1-dev_0.1.25-1+b1_i386 +libtut-dev_0.0.20070706-1_all +libtuxcap-dev_1.4.0.dfsg2-2.1_i386 +libtwin-dev_12.04.13.17.57-g130ee5f-2_i386 +libtwofish-dev_0.3-3_i386 +libtwolame-dev_0.3.13-1_i386 +libtxc-dxtn-s2tc-dev_0~git20110809-3_i386 +libtype-conv-camlp4-dev_3.0.4-1_i386 +libtyxml-ocaml-dev_2.1-1_i386 +libuchardet-dev_0.0.1-1_i386 +libucimf-dev_2.3.8-4_i386 +libucl-dev_1.03-5_i386 +libuclmmbase1-dev_1.2.16.0-1_i386 +libucommon-dev_5.2.2-4_i386 +libucto1-dev_0.5.2-2_i386 +libudev-dev_175-7.2_i386 +libudf-dev_0.83-4_i386 +libudt-dev_4.10+dfsg-1_i386 +libudunits2-dev_2.1.23-3_i386 +libuhd-dev_3.4.2-1_i386 +libuim-dev_1:1.8.1-4_i386 +libumlib-dev_0.8.2-1_i386 +libunac1-dev_1.8.0-6_i386 +libunbound-dev_1.4.17-3_i386 +libunicap2-dev_0.9.12-2_i386 +libuninameslist-dev_0.0.20091231-1.1_i386 +libuninum-dev_2.7-1.1_i386 +libunique-3.0-dev_3.0.2-1_i386 +libunique-dev_1.1.6-4_i386 +libunistring-dev_0.9.3-5_i386 +libunittest++-dev_1.4.0-3_i386 +libunshield-dev_0.6-3_i386 +libunwind-setjmp0-dev_0.99-0.3_i386 +libunwind7-dev_0.99-0.3_i386 +libupnp-dev_1:1.6.17-1.2_all +libupnp4-dev_1.8.0~svn20100507-1.2_i386 +libupnp6-dev_1:1.6.17-1.2_i386 +libupower-glib-dev_0.9.17-1_i386 +libupsclient1-dev_2.6.4-2.3+deb7u1_i386 +libupse-dev_1.0.0-1_i386 +libuptimed-dev_1:0.3.17-3.1_i386 +liburcu-dev_0.6.7-2_i386 +liburfkill-glib-dev_0.3.0-1_i386 +liburg0-dev_0.8.12-4_i386 +liburiparser-dev_0.7.5-1_i386 +libusb++-dev_2:0.1.12-20+nmu1_i386 +libusb-1.0-0-dev_2:1.0.11-1_i386 +libusb-dev_2:0.1.12-20+nmu1_i386 +libusb-ocaml-dev_1.2.0-2+b7_i386 +libusbip-dev_1.1.1+3.2.17-1_i386 +libusbmuxd-dev_1.0.7-2_i386 +libusbprog-dev_0.2.0-2_i386 +libusbredirhost-dev_0.4.3-2_i386 +libusbredirparser-dev_0.4.3-2_i386 +libusbtc08-dev_1.7.2-1_i386 +libuser1-dev_1:0.56.9.dfsg.1-1.2_i386 +libust-dev_2.0.4-1_i386 +libustr-dev_1.0.4-3_i386 +libutempter-dev_1.1.5-4_i386 +libuu-dev_0.5.20-3.3_i386 +libuuidm-ocaml-dev_0.9.4-1_i386 +libv4l-dev_0.8.8-3_i386 +libv8-dev_3.8.9.20-2_i386 +libv8-i18n-dev_0~0.svn7-3_i386 +libva-dev_1.0.15-4_i386 +libvala-0.14-dev_0.14.2-2_i386 +libvala-0.16-dev_0.16.1-2_i386 +libvaladoc-dev_0.3.2~git20120227-1_i386 +libvalhalla-dev_2.0.0-4+b1_i386 +libvanessa-adt-dev_0.0.9-1_i386 +libvanessa-logger-dev_0.0.10-1.1_i386 +libvanessa-socket-dev_0.0.12-1_i386 +libvarconf-dev_0.6.7-2_i386 +libvarnishapi-dev_3.0.2-2+deb7u1_i386 +libvbr-dev_2.6.8-4_i386 +libvc-dev_003.dfsg.1-12_i386 +libvcdinfo-dev_0.7.24+dfsg-0.1_i386 +libvde-dev_2.3.2-4_i386 +libvdeplug-dev_2.3.2-4_i386 +libvdk2-dev_2.4.0-5.3_i386 +libvdkbuilder2-dev_2.4.0-4.3_i386 +libvdkxdb2-dev_2.4.0-3.4_i386 +libvdpau-dev_0.4.1-7_i386 +libventrilo-dev_1.2.4-1_i386 +libverbiste-dev_0.1.34-1_i386 +libverto-dev_0.2.2-1_i386 +libvformat-dev_1.13-10_i386 +libvia-dev_2.0.4-2_i386 +libvibrant6-dev_6.1.20120620-2_i386 +libview-dev_0.6.6-2.1_i386 +libvigraimpex-dev_1.7.1+dfsg1-3_i386 +libvips-dev_7.28.5-1+deb7u1_i386 +libvirt-dev_0.9.12.3-1_i386 +libvirt-glib-1.0-dev_0.0.8-1_i386 +libvirt-ocaml-dev_0.6.1.2-1_i386 +libvisca-dev_1.0.1-1_i386 +libvisio-dev_0.0.17-1_i386 +libvisual-0.4-dev_0.4.0-5_i386 +libvlc-dev_2.0.3-5_i386 +libvlccore-dev_2.0.3-5_i386 +libvmmlib-dev_1.0-2_all +libvncserver-dev_0.9.9+dfsg-1_i386 +libvo-aacenc-dev_0.1.2-1_i386 +libvo-amrwbenc-dev_0.1.2-1_i386 +libvoaacenc-ocaml-dev_0.1.0-1+b1_i386 +libvoikko-dev_3.5-1.1_i386 +libvolpack1-dev_1.0b3-3_i386 +libvorbis-dev_1.3.2-1.3_i386 +libvorbis-ocaml-dev_0.6.1-1+b1_i386 +libvorbisidec-dev_1.0.2+svn18153-0.2_i386 +libvotequorum-dev_1.4.2-3_i386 +libvpb-dev_4.2.55-1_i386 +libvpx-dev_1.1.0-1_i386 +libvrb0-dev_0.5.1-5.1_i386 +libvte-2.90-dev_1:0.32.2-1_i386 +libvte-dev_1:0.28.2-5_i386 +libvte0.16-cil-dev_2.26.0-8_i386 +libvtk5-dev_5.8.0-13+b1_i386 +libvtk5-qt4-dev_5.8.0-13+b1_i386 +libvtkedge-dev_0.2.0~20110819-2_i386 +libvtkgdcm2-dev_2.2.0-14.1_i386 +libvxl1-dev_1.14.0-18_i386 +libwacom-dev_0.6-1_i386 +libwaei-dev_3.4.3-1_i386 +libwaili-dev_19990723-20_i386 +libwavefront-standalone3.0-dev_3.0.2+dfsg-4+b1_i386 +libwavpack-dev_4.60.1-3_i386 +libwayland-dev_0.85.0-2_i386 +libwbclient-dev_2:3.6.6-6+deb7u3_i386 +libwbxml2-dev_0.10.7-1_i386 +libwcstools-dev_3.8.5-1_i386 +libwebauth-dev_4.1.1-2_i386 +libwebcam0-dev_0.2.2-1_i386 +libwebkit-cil-dev_0.3-6_all +libwebkit-dev_1.8.1-3.4_i386 +libwebkitgtk-3.0-dev_1.8.1-3.4_i386 +libwebkitgtk-dev_1.8.1-3.4_i386 +libwebp-dev_0.1.3-3+nmu1_i386 +libwebrtc-audio-processing-dev_0.1-2_i386 +libweed-dev_1.6.2~ds1-2_i386 +libwfmath-0.3-dev_0.3.12-3_i386 +libwfut-0.2-dev_0.2.1-2_i386 +libwibble-dev_0.1.28-1.1_i386 +libwildmidi-dev_0.2.3.4-2.1_i386 +libwine-dev_1.4.1-4_i386 +libwings-dev_0.95.3-2_i386 +libwireshark-dev_1.8.2-5wheezy10_i386 +libwiretap-dev_1.8.2-5wheezy10_i386 +libwmf-dev_0.2.8.4-10.3_i386 +libwnck-3-dev_3.4.2-1_i386 +libwnck-dev_2.30.7-1_i386 +libwnck1.0-cil-dev_2.26.0-8_i386 +libwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libwnn6-dev_1.0.0-14.2+b1_i386 +libwpd-dev_0.9.4-3_i386 +libwpg-dev_0.2.1-1_i386 +libwps-dev_0.2.7-1_i386 +libwrap0-dev_7.6.q-24_i386 +libwraster3-dev_0.95.3-2_i386 +libwreport-dev_2.4-1_i386 +libwsutil-dev_1.8.2-5wheezy10_i386 +libwt-dev_3.2.1-2_i386 +libwtdbo-dev_3.2.1-2_i386 +libwtdbofirebird-dev_3.2.1-2_i386 +libwtdbopostgres-dev_3.2.1-2_i386 +libwtdbosqlite-dev_3.2.1-2_i386 +libwtext-dev_3.2.1-2_i386 +libwtfcgi-dev_3.2.1-2_i386 +libwthttp-dev_3.2.1-2_i386 +libwttest-dev_3.2.1-2_i386 +libwv-dev_1.2.9-3_i386 +libwv2-dev_0.4.2.dfsg.2-1~deb7u1_i386 +libwvstreams-dev_4.6.1-5_i386 +libwxbase2.8-dev_2.8.12.1-12_i386 +libwxgtk2.8-dev_2.8.12.1-12_i386 +libwxsmithlib-dev_10.05-2.1_i386 +libwxsmithlib0-dev_10.05-2.1_i386 +libwxsqlite3-2.8-dev_3.0.0.1~dfsg0-2_i386 +libwxsvg-dev_2:1.1.8~dfsg0-2_i386 +libx11-dev_2:1.5.0-1+deb7u1_i386 +libx11-xcb-dev_2:1.5.0-1+deb7u1_i386 +libx264-dev_2:0.123.2189+git35cf912-1_i386 +libx52pro-dev_0.1.1-2.1_i386 +libx86-dev_1.1+ds1-10_i386 +libxalan110-dev_1.10-6_i386 +libxapian-dev_1.2.12-2_i386 +libxatracker-dev_8.0.5-4+deb7u2_i386 +libxau-dev_1:1.0.7-1_i386 +libxaw7-dev_2:1.0.10-2_i386 +libxbae-dev_4.60.4-3_i386 +libxbase2.0-dev_2.0.0-8.5_i386 +libxcb-composite0-dev_1.8.1-2+deb7u1_i386 +libxcb-damage0-dev_1.8.1-2+deb7u1_i386 +libxcb-dpms0-dev_1.8.1-2+deb7u1_i386 +libxcb-dri2-0-dev_1.8.1-2+deb7u1_i386 +libxcb-ewmh-dev_0.3.9-2_i386 +libxcb-glx0-dev_1.8.1-2+deb7u1_i386 +libxcb-icccm4-dev_0.3.9-2_i386 +libxcb-image0-dev_0.3.9-1_i386 +libxcb-keysyms1-dev_0.3.9-1_i386 +libxcb-randr0-dev_1.8.1-2+deb7u1_i386 +libxcb-record0-dev_1.8.1-2+deb7u1_i386 +libxcb-render-util0-dev_0.3.8-1.1_i386 +libxcb-render0-dev_1.8.1-2+deb7u1_i386 +libxcb-res0-dev_1.8.1-2+deb7u1_i386 +libxcb-screensaver0-dev_1.8.1-2+deb7u1_i386 +libxcb-shape0-dev_1.8.1-2+deb7u1_i386 +libxcb-shm0-dev_1.8.1-2+deb7u1_i386 +libxcb-sync0-dev_1.8.1-2+deb7u1_i386 +libxcb-util0-dev_0.3.8-2_i386 +libxcb-xevie0-dev_1.8.1-2+deb7u1_i386 +libxcb-xf86dri0-dev_1.8.1-2+deb7u1_i386 +libxcb-xfixes0-dev_1.8.1-2+deb7u1_i386 +libxcb-xinerama0-dev_1.8.1-2+deb7u1_i386 +libxcb-xprint0-dev_1.8.1-2+deb7u1_i386 +libxcb-xtest0-dev_1.8.1-2+deb7u1_i386 +libxcb-xv0-dev_1.8.1-2+deb7u1_i386 +libxcb-xvmc0-dev_1.8.1-2+deb7u1_i386 +libxcb1-dev_1.8.1-2+deb7u1_i386 +libxcomp-dev_3.5.0.12-1+b1_i386 +libxcomposite-dev_1:0.4.3-2_i386 +libxcp-ocaml-dev_0.5.2-3+b1_i386 +libxcrypt-dev_1:2.4-3_i386 +libxcursor-dev_1:1.1.13-1+deb7u1_i386 +libxdamage-dev_1:1.1.3-2_i386 +libxdb-dev_1.2.0-7.2_i386 +libxdelta2-dev_1.1.3-9_i386 +libxdffileio-dev_0.3-1_i386 +libxdg-basedir-dev_1.1.1-2_i386 +libxdmcp-dev_1:1.1.1-1_i386 +libxdmf-dev_2.1.dfsg.1-5_i386 +libxdo-dev_1:2.20100701.2961-3+deb7u3_i386 +libxen-dev_4.1.4-3+deb7u1_i386 +libxen-ocaml-dev_4.1.4-3+deb7u1_i386 +libxenapi-ocaml-dev_1.3.2-15_i386 +libxenomai-dev_2.6.0-2_i386 +libxerces-c-dev_3.1.1-3_i386 +libxerces-c2-dev_2.8.0+deb1-3_i386 +libxext-dev_2:1.3.1-2+deb7u1_i386 +libxfce4menu-0.1-dev_4.6.2-1_i386 +libxfce4ui-1-dev_4.8.1-1_i386 +libxfce4util-dev_4.8.2-1_i386 +libxfcegui4-dev_4.8.1-5_i386 +libxfconf-0-dev_4.8.1-1_i386 +libxfixes-dev_1:5.0-4+deb7u1_i386 +libxfont-dev_1:1.4.5-3_i386 +libxft-dev_2.3.1-1_i386 +libxi-dev_2:1.6.1-1+deb7u1_i386 +libxine-dev_1.1.21-1+deb7u1_i386 +libxine2-dev_1.2.2-5_i386 +libxinerama-dev_2:1.1.2-1+deb7u1_i386 +libxkbfile-dev_1:1.0.8-1_i386 +libxklavier-dev_5.2.1-1_i386 +libxml++2.6-dev_2.34.2-1_i386 +libxml-light-ocaml-dev_2.2-15_i386 +libxml-security-c-dev_1.6.1-5+deb7u2_i386 +libxml2-dev_2.8.0+dfsg1-7+nmu3_i386 +libxmlada4.1-dev_4.1-2_i386 +libxmlezout2-dev_1.06.1-5_i386 +libxmlm-ocaml-dev_1.1.0-1_i386 +libxmlplaylist-ocaml-dev_0.1.3-1+b2_i386 +libxmlrpc-c++4-dev_1.16.33-3.2_i386 +libxmlrpc-c3-dev_1.16.33-3.2_all +libxmlrpc-core-c3-dev_1.16.33-3.2_i386 +libxmlrpc-epi-dev_0.54.2-1_i386 +libxmlrpc-light-ocaml-dev_0.6.1-3+b5_i386 +libxmlsec1-dev_1.2.18-2_i386 +libxmltok1-dev_1.2-3_i386 +libxmltooling-dev_1.4.2-5_i386 +libxmmsclient++-dev_0.8+dfsg-4_i386 +libxmmsclient++-glib-dev_0.8+dfsg-4_i386 +libxmmsclient-dev_0.8+dfsg-4_i386 +libxmmsclient-glib-dev_0.8+dfsg-4_i386 +libxmpi4-dev_2.2.3b8-13_i386 +libxmu-dev_2:1.1.1-1_i386 +libxmuu-dev_2:1.1.1-1_i386 +libxnee-dev_3.13-1_i386 +libxneur-dev_0.15.0-1.1_i386 +libxosd-dev_2.2.14-2_i386 +libxp-dev_1:1.0.1-2+deb7u1_i386 +libxpa-dev_2.1.14-2_i386 +libxplc0.3.13-dev_0.3.13-3_i386 +libxpm-dev_1:3.5.10-1_i386 +libxqdbm-dev_1.8.78-2_i386 +libxqilla-dev_2.3.0-1_i386 +libxr1-dev_1.0-2.1_i386 +libxrandr-dev_2:1.3.2-2+deb7u1_i386 +libxrender-dev_1:0.9.7-1+deb7u1_i386 +libxres-dev_2:1.0.6-1+deb7u1_i386 +libxsettings-client-dev_0.17-6_i386 +libxsettings-dev_0.11-3_i386 +libxslt1-dev_1.1.26-14.1_i386 +libxss-dev_1:1.2.2-1_i386 +libxstr-ocaml-dev_0.2.1-21+b3_i386 +libxstrp4-camlp4-dev_1.8-3_all +libxt-dev_1:1.1.3-1+deb7u1_i386 +libxtst-dev_2:1.2.1-1+deb7u1_i386 +libxv-dev_2:1.0.7-1+deb7u1_i386 +libxvidcore-dev_2:1.3.2-9_i386 +libxvmc-dev_2:1.0.7-1+deb7u2_i386 +libxxf86dga-dev_2:1.1.3-2+deb7u1_i386 +libxxf86vm-dev_1:1.1.2-1+deb7u1_i386 +libxy-dev_0.8-1+b1_i386 +libyahoo2-dev_1.0.1-1_i386 +libyajl-dev_2.0.4-2_i386 +libyaml-cpp-dev_0.3.0-1_i386 +libyaml-dev_0.1.4-2+deb7u4_i386 +libyaz4-dev_4.2.30-2_i386 +libyelp-dev_3.4.2-1+b1_i386 +libygl4-dev_4.2e-4_i386 +libykclient-dev_2.6-1_i386 +libykpers-1-dev_1.7.0-1_i386 +libyojson-ocaml-dev_1.0.3-1_i386 +libytnef0-dev_1.5-4_i386 +libyubikey-dev_1.8-1_i386 +libz80ex-dev_1.1.19-3_i386 +libzarith-ocaml-dev_1.1-2_i386 +libzbar-dev_0.10+doc-8_i386 +libzbargtk-dev_0.10+doc-8_i386 +libzbarqt-dev_0.10+doc-8_i386 +libzeep-dev_2.9.0-2_i386 +libzeitgeist-cil-dev_0.8.0.0-4_all +libzeitgeist-dev_0.3.18-1_i386 +libzen-dev_0.4.27-2_i386 +libzephyr-dev_3.0.2-2_i386 +libzerg0-dev_1.0.7-3_i386 +libzeroc-ice34-dev_3.4.2-8.2_i386 +libzinnia-dev_0.06-1+b1_i386 +libzip-dev_0.10.1-1.1_i386 +libzip-ocaml-dev_1.04-6+b3_i386 +libzipios++-dev_0.1.5.9+cvs.2007.04.28-5.1_i386 +libzita-alsa-pcmi-dev_0.2.0-1_i386 +libzita-convolver-dev_3.1.0-2_i386 +libzita-resampler-dev_1.1.0-3_i386 +libzlcore-dev_0.12.10dfsg-8_i386 +libzltext-dev_0.12.10dfsg-8_i386 +libzmq-dev_2.2.0+dfsg-2_i386 +libzn-poly-dev_0.8-1.1_i386 +libzookeeper-mt-dev_3.3.5+dfsg1-2_i386 +libzookeeper-st-dev_3.3.5+dfsg1-2_i386 +libzorp-dev_3.9.5-4_i386 +libzorpll-dev_3.9.1.3-1_i386 +libzrtpcpp-dev_2.0.0-3_i386 +libzthread-dev_2.3.2-7_i386 +libzvbi-dev_0.2.33-6_i386 +libzzip-dev_0.13.56-1.1_i386 +licq-dev_1.6.1-3_all +linux-libc-dev_3.2.57-3_i386 +lldpad-dev_0.9.44-1_i386 +llvm-2.9-dev_2.9+dfsg-7_i386 +llvm-3.0-dev_3.0-10_i386 +llvm-3.1-dev_3.1-1_i386 +llvm-dev_1:3.0-14+nmu2_i386 +lttv-dev_0.12.38-21032011-1+b1_i386 +lua-apr-dev_0.23.2-1_i386 +lua-bitop-dev_1.0.2-1_i386 +lua-curl-dev_0.3.0-7_i386 +lua-curses-dev_5.1.19-2_i386 +lua-cyrussasl-dev_1.0.0-4_i386 +lua-dbi-mysql-dev_0.5+svn78-4_i386 +lua-dbi-postgresql-dev_0.5+svn78-4_i386 +lua-dbi-sqlite3-dev_0.5+svn78-4_i386 +lua-event-dev_0.4.1-2_i386 +lua-expat-dev_1.2.0-5+deb7u1_i386 +lua-filesystem-dev_1.5.0+16+g84f1af5-1_i386 +lua-iconv-dev_7-1_i386 +lua-ldap-dev_1.1.0-1-geeac494-3_i386 +lua-leg-dev_0.1.2-8_all +lua-lgi-dev_0.6.2-1_i386 +lua-lpeg-dev_0.10.2-5_i386 +lua-md5-dev_1.1.2-6_i386 +lua-penlight-dev_1.0.2+htmldoc-2_all +lua-posix-dev_5.1.19-2_i386 +lua-rex-onig-dev_2.6.0-2_i386 +lua-rex-pcre-dev_2.6.0-2_i386 +lua-rex-posix-dev_2.6.0-2_i386 +lua-rex-tre-dev_2.6.0-2_i386 +lua-rings-dev_1.2.3-1_i386 +lua-sec-dev_0.4.1-1_i386 +lua-socket-dev_2.0.2-8_i386 +lua-sql-mysql-dev_2.3.0-1+build0_i386 +lua-sql-postgres-dev_2.3.0-1+build0_i386 +lua-sql-sqlite3-dev_2.3.0-1+build0_i386 +lua-svn-dev_0.4.0-7_i386 +lua-wsapi-fcgi-dev_1.5-3_i386 +lua-zip-dev_1.2.3-11_i386 +lua-zlib-dev_0.2-1_i386 +lua5.1-policy-dev_33_all +lv2-dev_1.0.0~dfsg2-2_i386 +lxc-dev_0.8.0~rc1-8+deb7u2_i386 +lzma-dev_9.22-2_all +manpages-de-dev_1.2-1_all +manpages-dev_3.44-1_all +manpages-fr-dev_3.44d1p1-1_all +manpages-ja-dev_0.5.0.0.20120606-1_all +manpages-pl-dev_1:0.3-1_all +manpages-pt-dev_20040726-4_all +matlab-support-dev_0.0.18_all +mdbtools-dev_0.7-1+deb7u1_i386 +med-bio-dev_1.13.2_all +med-imaging-dev_1.13.2_all +mesa-common-dev_8.0.5-4+deb7u2_i386 +mffm-fftw-dev_1.7-3_i386 +mffm-timecode-dev_1.6-2_all +mingw-w64-dev_2.0.3-1_all +mingw-w64-i686-dev_2.0.3-1_all +mingw-w64-x86-64-dev_2.0.3-1_all +minpack-dev_19961126+dfsg1-1_i386 +mongodb-dev_1:2.0.6-1.1_i386 +mpi-default-dev_1.0.1_i386 +muroard-dev_0.1.10-2_i386 +neko-dev_1.8.1-6_all +nettle-dev_2.4-3_i386 +network-manager-dev_0.9.4.0-10_i386 +ntfs-3g-dev_1:2012.1.15AR.5-2.1_i386 +ocfs2-tools-dev_1.6.4-1+deb7u1_i386 +ocl-icd-dev_1.3-3_i386 +ocl-icd-opencl-dev_1.3-3_i386 +ocsigen-dev_1.3.4-2_all +octave-pkg-dev_1.0.2_all +ofono-dev_1.6-2_all +okteta-dev_4:4.8.4+dfsg-1_i386 +okular-dev_4:4.8.4-3_i386 +open-vm-tools-dev_2:8.8.0+2012.05.21-724730-1+nmu2_all +openais-dev_1.1.4-4.1_i386 +openbox-dev_3.5.0-7_i386 +openchangeserver-dev_1:1.0-3_i386 +oss4-dev_4.2-build2006-2+deb7u1_all +pacemaker-dev_1.1.7-1_i386 +packaging-dev_0.4_all +paraview-dev_3.14.1-6_i386 +parole-dev_0.2.0.6-1+b1_i386 +parser3-dev_3.4.2-2_i386 +pbs-drmaa-dev_1.0.10-3_i386 +petsc-dev_3.2.dfsg-6_all +php5-dev_5.4.4-14+deb7u9_i386 +pidgin-dev_2.10.9-1~deb7u1_all +pinball-dev_0.3.1-13.1_i386 +planetpenguin-racer-gimp-dev_0.4-5_all +planner-dev_0.14.6-1_i386 +plplot-tcl-dev_5.9.9-5_i386 +plptools-dev_1.0.9-2.4_i386 +plymouth-dev_0.8.5.1-5_i386 +portaudio19-dev_19+svn20111121-1_i386 +postfix-dev_2.9.6-2_all +ppl-dev_0.11.2-8_i386 +ppp-dev_2.4.5-5.1_all +prayer-templates-dev_1.3.4-dfsg1-1_i386 +proftpd-dev_1.3.4a-5+deb7u1_i386 +pslib-dev_0.4.5-3_i386 +publib-dev_0.40-1_i386 +puredata-dev_0.43.2-5_all +pvm-dev_3.4.5-12.5_i386 +pxlib-dev_0.6.5-1_i386 +python-all-dev_2.7.3-4+deb7u1_all +python-apt-dev_0.8.8.2_all +python-cairo-dev_1.8.8-1_all +python-cxx-dev_6.2.4-3_all +python-dbus-dev_1.1.1-1_all +python-dev_2.7.3-4+deb7u1_all +python-egenix-mx-base-dev_3.2.1-1.1_all +python-gamera-dev_3.3.3-2_all +python-gi-dev_3.2.2-2_all +python-gnome2-desktop-dev_2.32.0+dfsg-2_all +python-gnome2-dev_2.28.1+dfsg-1_all +python-gnome2-extras-dev_2.25.3-12_all +python-gobject-2-dev_2.28.6-10_all +python-gobject-dev_3.2.2-2_all +python-greenlet-dev_0.3.1-2.5_i386 +python-gst0.10-dev_0.10.22-3_i386 +python-gtk2-dev_2.24.0-3_all +python-kde4-dev_4:4.8.4-1_all +python-ldb-dev_1:1.1.6-1_i386 +python-openturns-dev_1.0-4_i386 +python-pyorbit-dev_2.24.0-6_all +python-qt4-dev_4.9.3-4_all +python-sip-dev_4.13.3-2_i386 +python-talloc-dev_2.0.7+git20120207-1_i386 +python-webkit-dev_1.1.8-2_all +python2.6-dev_2.6.8-1.1_i386 +python2.7-dev_2.7.3-6+deb7u2_i386 +python3-all-dev_3.2.3-6_all +python3-cairo-dev_1.10.0+dfsg-2_all +python3-cxx-dev_6.2.4-3_all +python3-dev_3.2.3-6_all +python3-sip-dev_4.13.3-2_i386 +python3.2-dev_3.2.3-7_i386 +qmf-dev_1.0.7~2011w23.2-2.1_i386 +qtmobility-dev_1.2.0-3_i386 +r-base-dev_2.15.1-4_all +regina-normal-dev_4.93-1_i386 +resource-agents-dev_1:3.9.2-5+deb7u2_i386 +rhythmbox-dev_2.97-2.1_i386 +rivet-plugins-dev_1.8.0-1_all +roarplaylistd-dev_0.1.1-2_all +robot-player-dev_3.0.2+dfsg-4_all +ruby-dev_1:1.9.3_all +ruby-gnome2-dev_1.1.3-2+b1_i386 +ruby1.8-dev_1.8.7.358-7.1+deb7u1_i386 +ruby1.9.1-dev_1.9.3.194-8.1+deb7u2_i386 +rygel-1.0-dev_0.14.3-2+deb7u1_i386 +samba4-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +sbnc-php-dev_1.2-26_all +science-astronomy-dev_1.0_all +science-dataacquisition-dev_1.0_all +science-engineering-dev_1.0_all +science-highenergy-physics-dev_1.0_all +science-mathematics-dev_1.0_all +science-meteorology-dev_1.0_all +science-nanoscale-physics-dev_1.0_all +science-physics-dev_1.0_all +scim-dev_1.4.13-5_all +sciplot-dev_1.36-15_i386 +selinux-policy-dev_2:2.20110726-12_all +seqan-dev_1.3.1-1_all +sfftw-dev_2.1.5-1_i386 +skalibs-dev_0.47-1_i386 +slurm-drmaa-dev_1.0.4-3_i386 +slurm-llnl-basic-plugins-dev_2.3.4-2+b1_i386 +snappea-dev_3.0d3-22_i386 +spl-dev_1.0~pre6-3.1+b1_i386 +sra-toolkit-libs-dev_2.1.7a-1_i386 +ss-dev_2.0-1.42.5-1.1_i386 +stx-btree-dev_0.8.6-1_all +styx-dev_1.8.0-1.1_i386 +supercollider-dev_1:3.4.5-1wheezy1_i386 +svdrpservice-dev_0.0.4-14_all +sweep-dev_0.9.3-6_all +swish-e-dev_2.4.7-3_i386 +syfi-dev_1.0.0.dfsg-1_all +system-tools-backends-dev_2.10.2-1_all +systemtap-sdt-dev_1.7-1+deb7u1_i386 +tcl-dev_8.5.0-2.1_all +tcl-memchan-dev_2.3-2_i386 +tcl-trf-dev_2.1.4-dfsg1-1_i386 +tcl8.4-dev_8.4.19-5_i386 +tcl8.5-dev_8.5.11-2_i386 +tclcl-dev_1.20-6_all +tclx8.4-dev_8.4.0-3_i386 +tclxml-dev_3.3~svn11-2_i386 +tdom-dev_0.8.3~20080525-3+nmu2_i386 +tesseract-ocr-dev_3.02.01-6_all +tix-dev_8.4.3-4_i386 +tk-dev_8.5.0-2.1_all +tk8.4-dev_8.4.19-5_i386 +tk8.5-dev_8.5.11-2_i386 +tqsllib-dev_2.2-5_i386 +trafficserver-dev_3.0.5-1_i386 +tuxpaint-dev_1:0.9.21-1.1_all +unagi-dev_0.3.3-2_i386 +unixodbc-dev_2.2.14p2-5_i386 +uthash-dev_1.9.5-1_all +uuid-dev_2.20.1-5.3_i386 +vdr-dev_1.7.28-1_all +vflib3-dev_3.6.14.dfsg-3+b1_i386 +voms-dev_2.0.8-1_i386 +vstream-client-dev_1.2-6.1_i386 +wcslib-dev_4.13.4-1_i386 +weechat-dev_0.3.8-1+deb7u1_all +wireshark-dev_1.8.2-5wheezy10_i386 +witty-dev_3.2.1-2_all +wordnet-dev_1:3.0-29_i386 +wzdftpd-dev_0.8.3-6.2_i386 +x11proto-bigreqs-dev_1:1.1.2-1_all +x11proto-composite-dev_1:0.4.2-2_all +x11proto-core-dev_7.0.23-1_all +x11proto-damage-dev_1:1.2.1-2_all +x11proto-dmx-dev_1:2.3.1-2_all +x11proto-dri2-dev_2.6-2_all +x11proto-fixes-dev_1:5.0-2_all +x11proto-fonts-dev_2.1.2-1_all +x11proto-gl-dev_1.4.15-1_all +x11proto-input-dev_2.2-1_all +x11proto-kb-dev_1.0.6-2_all +x11proto-print-dev_1.0.5-2_all +x11proto-randr-dev_1.3.2-2_all +x11proto-record-dev_1.14.2-1_all +x11proto-render-dev_2:0.11.1-2_all +x11proto-resource-dev_1.2.0-3_all +x11proto-scrnsaver-dev_1.2.2-1_all +x11proto-video-dev_2.3.1-2_all +x11proto-xcmisc-dev_1.2.2-1_all +x11proto-xext-dev_7.2.1-1_all +x11proto-xf86bigfont-dev_1.2.0-3_all +x11proto-xf86dga-dev_2.1-3_all +x11proto-xf86dri-dev_2.1.1-2_all +x11proto-xf86vidmode-dev_2.3.1-2_all +x11proto-xinerama-dev_1.2.1-2_all +xaw3dg-dev_1.5+E-18.2_i386 +xbmc-eventclients-dev_2:11.0~git20120510.82388d5-1_all +xfce4-panel-dev_4.8.6-4_i386 +xfslibs-dev_3.1.7+b1_i386 +xmhtml1-dev_1.1.7-18_i386 +xmms2-dev_0.8+dfsg-4_all +xorg-dev_1:7.7+3~deb7u1_all +xotcl-dev_1.6.7-2_i386 +xpaint-dev_2.9.1.4-3+b2_i386 +xserver-xorg-dev_2:1.12.4-6+deb7u2_i386 +xserver-xorg-input-evdev-dev_1:2.7.0-1_all +xserver-xorg-input-joystick-dev_1:1.6.1-1_all +xserver-xorg-input-synaptics-dev_1.6.2-2_all +xtrans-dev_1.2.7-1_all +xulrunner-dev_24.4.0esr-1~deb7u2_i386 +xutils-dev_1:7.7~1_i386 +xviewg-dev_3.2p1.4-28.1_i386 +yate-dev_4.1.0-1~dfsg-3_i386 +yorick-dev_2.2.02+dfsg-6_i386 +zathura-dev_0.1.2-4_all +zlib1g-dev_1:1.2.7.dfsg-13_i386 +znc-dev_0.206-2_i386 +zsh-dev_4.3.17-1_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t04_mirror/SearchMirror2Test_gold b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror2Test_gold new file mode 100644 index 00000000..7ad172a5 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror2Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: mirror with name mirror-xx not found diff --git a/src/github.com/smira/aptly/system/t04_mirror/SearchMirror3Test_gold b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror3Test_gold new file mode 100644 index 00000000..58f09611 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror3Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: parsing failed: unexpected token : expecting ')' diff --git a/src/github.com/smira/aptly/system/t04_mirror/SearchMirror4Test_gold b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror4Test_gold new file mode 100644 index 00000000..007f3b4f --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/SearchMirror4Test_gold @@ -0,0 +1,95 @@ + +coreutils_8.13-3.5_amd64 +coreutils_8.13-3.5_i386 +debconf_1.5.49_all +dpkg_1.16.12_amd64 +dpkg_1.16.12_i386 +fontconfig-config_2.9.0-7.1_all +fonts-freefont-ttf_20120503-1_all +gcc-4.7-base_4.7.2-5_amd64 +gcc-4.7-base_4.7.2-5_i386 +gsfonts-x11_0.22_all +gsfonts_1:8.11+urwcyr1.0.7~pre44-4.2_all +libacl1_2.2.51-8_amd64 +libacl1_2.2.51-8_i386 +libattr1_1:2.4.46-8_amd64 +libattr1_1:2.4.46-8_i386 +libbz2-1.0_1.0.6-4_amd64 +libbz2-1.0_1.0.6-4_i386 +libc-bin_2.13-38+deb7u1_amd64 +libc-bin_2.13-38+deb7u1_i386 +libc6_2.13-38+deb7u1_amd64 +libc6_2.13-38+deb7u1_i386 +libexpat1_2.1.0-1+deb7u1_amd64 +libexpat1_2.1.0-1+deb7u1_i386 +libfontconfig1_2.9.0-7.1_amd64 +libfontconfig1_2.9.0-7.1_i386 +libfontenc1_1:1.1.1-1_amd64 +libfontenc1_1:1.1.1-1_i386 +libfreetype6_2.4.9-1.1_amd64 +libfreetype6_2.4.9-1.1_i386 +libgcc1_1:4.7.2-5_amd64 +libgcc1_1:4.7.2-5_i386 +libgcrypt11_1.5.0-5+deb7u1_amd64 +libgcrypt11_1.5.0-5+deb7u1_i386 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_i386 +libgeoip1_1.4.8+dfsg-3_amd64 +libgeoip1_1.4.8+dfsg-3_i386 +libgpg-error0_1.10-3.1_amd64 +libgpg-error0_1.10-3.1_i386 +libjpeg8_8d-1_amd64 +libjpeg8_8d-1_i386 +liblzma5_5.1.1alpha+20120614-2_amd64 +liblzma5_5.1.1alpha+20120614-2_i386 +libpam0g_1.1.3-7.1_amd64 +libpam0g_1.1.3-7.1_i386 +libpcre3_1:8.30-5_amd64 +libpcre3_1:8.30-5_i386 +libpng12-0_1.2.49-1_amd64 +libpng12-0_1.2.49-1_i386 +libselinux1_2.1.9-5_amd64 +libselinux1_2.1.9-5_i386 +libssl1.0.0_1.0.1e-2+deb7u7_amd64 +libssl1.0.0_1.0.1e-2+deb7u7_i386 +libx11-6_2:1.5.0-1+deb7u1_amd64 +libx11-6_2:1.5.0-1+deb7u1_i386 +libx11-data_2:1.5.0-1+deb7u1_all +libxau6_1:1.0.7-1_amd64 +libxau6_1:1.0.7-1_i386 +libxcb1_1.8.1-2+deb7u1_amd64 +libxcb1_1.8.1-2+deb7u1_i386 +libxdmcp6_1:1.1.1-1_amd64 +libxdmcp6_1:1.1.1-1_i386 +libxfont1_1:1.4.5-3_amd64 +libxfont1_1:1.4.5-3_i386 +libxml2_2.8.0+dfsg1-7+nmu3_amd64 +libxml2_2.8.0+dfsg1-7+nmu3_i386 +libxpm4_1:3.5.10-1_amd64 +libxpm4_1:3.5.10-1_i386 +libxslt1.1_1.1.26-14.1_amd64 +libxslt1.1_1.1.26-14.1_i386 +lsb-base_4.1+Debian8+deb7u1_all +multiarch-support_2.13-38+deb7u1_amd64 +multiarch-support_2.13-38+deb7u1_i386 +nginx-common_1.2.1-2.2+wheezy2_all +nginx-full_1.2.1-2.2+wheezy2_amd64 +nginx-full_1.2.1-2.2+wheezy2_i386 +nginx-light_1.2.1-2.2+wheezy2_amd64 +nginx-light_1.2.1-2.2+wheezy2_i386 +nginx_1.2.1-2.2+wheezy2_all +perl-base_5.14.2-21+deb7u1_amd64 +perl-base_5.14.2-21+deb7u1_i386 +tar_1.26+dfsg-0.1_amd64 +tar_1.26+dfsg-0.1_i386 +ttf-bitstream-vera_1.10-8_all +ttf-dejavu-core_2.33-3_all +ucf_3.0025+nmu3_all +x11-common_1:7.7+3~deb7u1_all +xfonts-encodings_1:1.0.4-1_all +xfonts-utils_1:7.7~1_amd64 +xfonts-utils_1:7.7~1_i386 +zlib1g_1:1.2.7.dfsg-13_amd64 +zlib1g_1:1.2.7.dfsg-13_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold index d5542dde..94f4085f 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold @@ -4,6 +4,7 @@ Distribution: wheezy Components: main, contrib, non-free Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Last update: never Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror3Test_gold b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror3Test_gold index 94db9a7c..a8315473 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror3Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror3Test_gold @@ -4,7 +4,7 @@ Distribution: wheezy Components: contrib Architectures: i386, amd64 Download Sources: no -Last update: 2014-02-25 00:21:33 MSK +Download .udebs: no Number of packages: 325 Information from release file: diff --git a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror4Test_gold b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror4Test_gold index 7a8182b7..1412be26 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror4Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror4Test_gold @@ -4,6 +4,7 @@ Distribution: wheezy/updates Components: main Architectures: amd64, armel, armhf, i386, ia64, kfreebsd-amd64, kfreebsd-i386, mips, mipsel, powerpc, s390, s390x, sparc Download Sources: no +Download .udebs: no Filter: nginx | Priority (required) Filter With Deps: yes Last update: never @@ -12,11 +13,9 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: updates/main updates/contrib updates/non-free -Date: Sun, 13 Jul 2014 12:12:08 UTC Description: Debian 7.0 Security Updates Label: Debian-Security Origin: Debian Suite: stable -Valid-Until: Wed, 23 Jul 2014 12:12:08 UTC Version: 7.0 diff --git a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold new file mode 100644 index 00000000..9d4793a6 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold @@ -0,0 +1,21 @@ + + +Applying filter... +Building download queue... +Download queue: 5 items (0.54 MiB) +Downloading & parsing package files... +Downloading ftp://ftp.ru.debian.org/debian/dists/wheezy/InRelease... +Downloading ftp://ftp.ru.debian.org/debian/dists/wheezy/Release... +Downloading ftp://ftp.ru.debian.org/debian/dists/wheezy/Release.gpg... +Downloading ftp://ftp.ru.debian.org/debian/dists/wheezy/main/binary-i386/Packages.bz2... +Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sed/sed_4.2.1-10_i386.deb... +Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sensible-utils/sensible-utils_0.0.7_all.deb... +Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysv-rc_2.88dsf-41+deb7u1_all.deb... +Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-41+deb7u1_i386.deb... +Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysvinit_2.88dsf-41+deb7u1_i386.deb... +Mirror `wheezy-main` has been successfully updated. +Packages filtered: 36046 -> 5. +gpgv: Good signature from "Debian Archive Automatic Signing Key (7.0/wheezy) " +gpgv: Good signature from "Wheezy Stable Release Key " +gpgv: RSA key ID 46925553 +gpgv: RSA key ID 65FFB764 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror12Test_gold b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror12Test_gold new file mode 100644 index 00000000..1a7390dd --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror12Test_gold @@ -0,0 +1,33 @@ + + +Applying filter... +Building download queue... +Download queue: 10 items (0.76 MiB) +Downloading & parsing package files... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/InRelease... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/Release... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/main/binary-amd64/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/main/debian-installer/binary-amd64/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/main/debian-installer/binary-i386/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/non-free/binary-amd64/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/non-free/binary-i386/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/non-free/debian-installer/binary-amd64/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/dists/squeeze/non-free/debian-installer/binary-i386/Packages.bz2... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_amd64.deb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_i386.deb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_amd64.deb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_i386.deb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid1.0.0.rc16-udeb_1.0.0.rc16-4.1_amd64.udeb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid1.0.0.rc16-udeb_1.0.0.rc16-4.1_i386.udeb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_amd64.deb... +Downloading http://mirror.yandex.ru/debian/pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_i386.deb... +Mirror `squeeze` has been successfully updated. +Packages filtered: 45830 -> 10. +gpgv: Good signature from "Debian Archive Automatic Signing Key (6.0/squeeze) " +gpgv: Good signature from "Squeeze Stable Release Key " +gpgv: RSA key ID 473041FA +gpgv: RSA key ID B98321F9 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t04_mirror/__init__.py b/src/github.com/smira/aptly/system/t04_mirror/__init__.py index 075e3c8f..1c2c36bf 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/__init__.py +++ b/src/github.com/smira/aptly/system/t04_mirror/__init__.py @@ -9,3 +9,4 @@ from .update import * from .drop import * from .rename import * from .edit import * +from .search import * diff --git a/src/github.com/smira/aptly/system/t04_mirror/create.py b/src/github.com/smira/aptly/system/t04_mirror/create.py index 91575aa9..0e469e33 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/create.py +++ b/src/github.com/smira/aptly/system/t04_mirror/create.py @@ -273,3 +273,36 @@ class CreateMirror23Test(BaseTest): """ runCmd = "aptly mirror create -ignore-signatures -filter='nginx | ' mirror23 http://security.debian.org/ wheezy/updates main" expectedCode = 1 + + +class CreateMirror24Test(BaseTest): + """ + create mirror: disable config value with option + """ + runCmd = "aptly mirror create -ignore-signatures=false -keyring=aptlytest.gpg mirror24 http://security.debian.org/ wheezy/updates main" + fixtureGpg = True + outputMatchPrepare = lambda _, s: re.sub(r'Signature made .* using', '', s) + + configOverride = { + "gpgDisableVerify": True + } + + +class CreateMirror25Test(BaseTest): + """ + create mirror: mirror with udebs enabled + """ + runCmd = "aptly -architectures=i386 mirror create -ignore-signatures -with-udebs mirror25 http://mirror.yandex.ru/debian/ wheezy" + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show mirror25", "mirror_show") + + +class CreateMirror26Test(BaseTest): + """ + create mirror: flat mirror with udebs + """ + runCmd = "aptly mirror create -keyring=aptlytest.gpg -with-udebs mirror26 http://pkg.jenkins-ci.org/debian-stable binary/" + fixtureGpg = True + expectedCode = 1 diff --git a/src/github.com/smira/aptly/system/t04_mirror/edit.py b/src/github.com/smira/aptly/system/t04_mirror/edit.py index 6453ad11..53b36cbf 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/edit.py +++ b/src/github.com/smira/aptly/system/t04_mirror/edit.py @@ -4,10 +4,10 @@ from lib import BaseTest class EditMirror1Test(BaseTest): """ - edit mirror: enable filter + edit mirror: enable filter & download sources """ fixtureDB = True - runCmd = "aptly mirror edit -filter=nginx -filter-with-deps wheezy-main" + runCmd = "aptly mirror edit -filter=nginx -filter-with-deps -with-sources wheezy-main" def check(self): self.check_output() @@ -58,3 +58,46 @@ class EditMirror5Test(BaseTest): self.check_output() self.check_cmd_output("aptly mirror show mirror5", "mirror_show", match_prepare=removeDates) + + +class EditMirror6Test(BaseTest): + """ + edit mirror: change architectures + """ + fixtureDB = True + runCmd = "aptly mirror edit -architectures=amd64,s390 wheezy-main" + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show wheezy-main", "mirror_show", match_prepare=lambda s: re.sub(r"Last update: [0-9:+A-Za-z -]+\n", "", s)) + + +class EditMirror7Test(BaseTest): + """ + edit mirror: change architectures to missing archs + """ + fixtureDB = True + runCmd = "aptly mirror edit -architectures=amd64,x56 wheezy-main" + expectedCode = 1 + + +class EditMirror8Test(BaseTest): + """ + edit mirror: enable udebs + """ + fixtureDB = True + runCmd = "aptly mirror edit -with-udebs wheezy-main" + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show wheezy-main", "mirror_show", match_prepare=lambda s: re.sub(r"Last update: [0-9:+A-Za-z -]+\n", "", s)) + + +class EditMirror9Test(BaseTest): + """ + edit mirror: flat mirror with udebs + """ + fixtureCmds = ["aptly mirror create -keyring=aptlytest.gpg mirror9 http://pkg.jenkins-ci.org/debian-stable binary/"] + fixtureGpg = True + runCmd = "aptly mirror edit -with-udebs mirror9" + expectedCode = 1 diff --git a/src/github.com/smira/aptly/system/t04_mirror/search.py b/src/github.com/smira/aptly/system/t04_mirror/search.py new file mode 100644 index 00000000..a887fe29 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/search.py @@ -0,0 +1,36 @@ +from lib import BaseTest + + +class SearchMirror1Test(BaseTest): + """ + search mirror: regular search + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly mirror search wheezy-main '$$Architecture (i386), Name (% *-dev)'" + + +class SearchMirror2Test(BaseTest): + """ + search mirror: missing mirror + """ + runCmd = "aptly mirror search mirror-xx 'Name'" + expectedCode = 1 + + +class SearchMirror3Test(BaseTest): + """ + search mirror: wrong expression + """ + fixtureDB = True + expectedCode = 1 + runCmd = "aptly mirror search wheezy-main '$$Architecture (i386'" + + +class SearchMirror4Test(BaseTest): + """ + search mirror: with-deps search + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly mirror search -with-deps wheezy-main 'Name (nginx)'" diff --git a/src/github.com/smira/aptly/system/t04_mirror/update.py b/src/github.com/smira/aptly/system/t04_mirror/update.py index 430eee53..5a57aded 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/update.py +++ b/src/github.com/smira/aptly/system/t04_mirror/update.py @@ -140,3 +140,35 @@ class UpdateMirror10Test(BaseTest): def output_processor(self, output): return "\n".join(sorted(output.split("\n"))) + + +class UpdateMirror11Test(BaseTest): + """ + update mirrors: update over FTP + """ + longTest = False + fixtureGpg = True + fixtureCmds = [ + "aptly mirror create -keyring=aptlytest.gpg -filter='Priority (required), Name (% s*)' -architectures=i386 wheezy-main ftp://ftp.ru.debian.org/debian/ wheezy main", + ] + outputMatchPrepare = lambda _, s: re.sub(r'Signature made .* using', '', s) + runCmd = "aptly mirror update -keyring=aptlytest.gpg wheezy-main" + + def output_processor(self, output): + return "\n".join(sorted(output.split("\n"))) + + +class UpdateMirror12Test(BaseTest): + """ + update mirrors: update with udebs + """ + longTest = False + fixtureGpg = True + fixtureCmds = [ + "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (dmraid)' -with-udebs squeeze http://mirror.yandex.ru/debian/ squeeze main non-free", + ] + runCmd = "aptly mirror update -keyring=aptlytest.gpg squeeze" + outputMatchPrepare = lambda _, s: re.sub(r'Signature made .* using', '', s) + + def output_processor(self, output): + return "\n".join(sorted(output.split("\n"))) diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_gold new file mode 100644 index 00000000..63cad56b --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_gold @@ -0,0 +1,5 @@ +Loading packages (661)... +Building indexes... + +Snapshot snap2 successfully filtered. +You can run 'aptly publish snapshot snap2' to publish snapshot as Debian repository. diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_snapshot_show b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_snapshot_show new file mode 100644 index 00000000..bd085c4d --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot1Test_snapshot_show @@ -0,0 +1,8 @@ +Name: snap2 +Description: Filtered 'snap1', query was: 'mame unrar' +Number of packages: 4 +Packages: + mame_0.146-5_amd64 + unrar_1:4.1.4-1_amd64 + mame_0.146-5_i386 + unrar_1:4.1.4-1_i386 diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_gold new file mode 100644 index 00000000..a2956341 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_gold @@ -0,0 +1,5 @@ +Loading packages (4742)... +Building indexes... + +Snapshot snap2 successfully filtered. +You can run 'aptly publish snapshot snap2' to publish snapshot as Debian repository. diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_snapshot_show b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_snapshot_show new file mode 100644 index 00000000..677c33d1 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot2Test_snapshot_show @@ -0,0 +1,13 @@ +Name: snap2 +Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4)' +Number of packages: 9 +Packages: + init-system-helpers_1.18~bpo70+1_all + libestr0_0.1.9-1~bpo70+1_amd64 + libjson-c2_0.11-3~bpo7+1_amd64 + liblogging-stdlog0_1.0.4-1~bpo70+1_amd64 + rsyslog_7.6.3-2~bpo70+1_amd64 + libestr0_0.1.9-1~bpo70+1_i386 + libjson-c2_0.11-3~bpo7+1_i386 + liblogging-stdlog0_1.0.4-1~bpo70+1_i386 + rsyslog_7.6.3-2~bpo70+1_i386 diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_gold new file mode 100644 index 00000000..391ea639 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_gold @@ -0,0 +1,5 @@ +Loading packages (56121)... +Building indexes... + +Snapshot snap2 successfully filtered. +You can run 'aptly publish snapshot snap2' to publish snapshot as Debian repository. diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_snapshot_show b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_snapshot_show new file mode 100644 index 00000000..680283f2 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot3Test_snapshot_show @@ -0,0 +1,127 @@ +Name: snap2 +Description: Filtered 'snap1', query was: 'Priority (required) nginx xyz' +Number of packages: 123 +Packages: + debconf_1.5.49_all + debconf-i18n_1.5.49_all + libpam-runtime_1.1.3-7.1_all + libtext-wrapi18n-perl_0.06-7_all + lsb-base_4.1+Debian8+deb7u1_all + ncurses-base_5.9-10_all + nginx_1.2.1-2.2+wheezy2_all + nginx-naxsi-ui_1.2.1-2.2+wheezy2_all + sensible-utils_0.0.7_all + sysv-rc_2.88dsf-41+deb7u1_all + tzdata_2014a-0wheezy1_all + base-files_7.1wheezy5_amd64 + base-passwd_3.5.26_amd64 + bash_4.2+dfsg-0.1_amd64 + bsdutils_1:2.20.1-5.3_amd64 + coreutils_8.13-3.5_amd64 + dash_0.5.7-3_amd64 + debianutils_4.3.2_amd64 + diffutils_1:3.2-6_amd64 + dpkg_1.16.12_amd64 + e2fslibs_1.42.5-1.1_amd64 + e2fsprogs_1.42.5-1.1_amd64 + findutils_4.4.2-4_amd64 + gcc-4.7-base_4.7.2-5_amd64 + grep_2.12-2_amd64 + gzip_1.5-1.1_amd64 + hostname_3.11_amd64 + initscripts_2.88dsf-41+deb7u1_amd64 + libacl1_2.2.51-8_amd64 + libattr1_1:2.4.46-8_amd64 + libblkid1_2.20.1-5.3_amd64 + libc-bin_2.13-38+deb7u1_amd64 + libc6_2.13-38+deb7u1_amd64 + libcomerr2_1.42.5-1.1_amd64 + libgcc1_1:4.7.2-5_amd64 + liblocale-gettext-perl_1.05-7+b1_amd64 + liblzma5_5.1.1alpha+20120614-2_amd64 + libmount1_2.20.1-5.3_amd64 + libncurses5_5.9-10_amd64 + libpam-modules_1.1.3-7.1_amd64 + libpam-modules-bin_1.1.3-7.1_amd64 + libpam0g_1.1.3-7.1_amd64 + libselinux1_2.1.9-5_amd64 + libsepol1_2.1.4-3_amd64 + libss2_1.42.5-1.1_amd64 + libtext-charwidth-perl_0.04-7+b1_amd64 + libtext-iconv-perl_1.7-5_amd64 + libtinfo5_5.9-10_amd64 + libuuid1_2.20.1-5.3_amd64 + login_1:4.1.5.1-1_amd64 + mawk_1.3.3-17_amd64 + mount_2.20.1-5.3_amd64 + multiarch-support_2.13-38+deb7u1_amd64 + ncurses-bin_5.9-10_amd64 + nginx-extras_1.2.1-2.2+wheezy2_amd64 + nginx-full_1.2.1-2.2+wheezy2_amd64 + nginx-light_1.2.1-2.2+wheezy2_amd64 + nginx-naxsi_1.2.1-2.2+wheezy2_amd64 + passwd_1:4.1.5.1-1_amd64 + perl-base_5.14.2-21+deb7u1_amd64 + sed_4.2.1-10_amd64 + sysvinit_2.88dsf-41+deb7u1_amd64 + sysvinit-utils_2.88dsf-41+deb7u1_amd64 + tar_1.26+dfsg-0.1_amd64 + util-linux_2.20.1-5.3_amd64 + xz-utils_5.1.1alpha+20120614-2_amd64 + zlib1g_1:1.2.7.dfsg-13_amd64 + base-files_7.1wheezy5_i386 + base-passwd_3.5.26_i386 + bash_4.2+dfsg-0.1_i386 + bsdutils_1:2.20.1-5.3_i386 + coreutils_8.13-3.5_i386 + dash_0.5.7-3_i386 + debianutils_4.3.2_i386 + diffutils_1:3.2-6_i386 + dpkg_1.16.12_i386 + e2fslibs_1.42.5-1.1_i386 + e2fsprogs_1.42.5-1.1_i386 + findutils_4.4.2-4_i386 + gcc-4.7-base_4.7.2-5_i386 + grep_2.12-2_i386 + gzip_1.5-1.1_i386 + hostname_3.11_i386 + initscripts_2.88dsf-41+deb7u1_i386 + libacl1_2.2.51-8_i386 + libattr1_1:2.4.46-8_i386 + libblkid1_2.20.1-5.3_i386 + libc-bin_2.13-38+deb7u1_i386 + libc6_2.13-38+deb7u1_i386 + libcomerr2_1.42.5-1.1_i386 + libgcc1_1:4.7.2-5_i386 + liblocale-gettext-perl_1.05-7+b1_i386 + liblzma5_5.1.1alpha+20120614-2_i386 + libmount1_2.20.1-5.3_i386 + libncurses5_5.9-10_i386 + libpam-modules_1.1.3-7.1_i386 + libpam-modules-bin_1.1.3-7.1_i386 + libpam0g_1.1.3-7.1_i386 + libselinux1_2.1.9-5_i386 + libsepol1_2.1.4-3_i386 + libss2_1.42.5-1.1_i386 + libtext-charwidth-perl_0.04-7+b1_i386 + libtext-iconv-perl_1.7-5_i386 + libtinfo5_5.9-10_i386 + libuuid1_2.20.1-5.3_i386 + login_1:4.1.5.1-1_i386 + mawk_1.3.3-17_i386 + mount_2.20.1-5.3_i386 + multiarch-support_2.13-38+deb7u1_i386 + ncurses-bin_5.9-10_i386 + nginx-extras_1.2.1-2.2+wheezy2_i386 + nginx-full_1.2.1-2.2+wheezy2_i386 + nginx-light_1.2.1-2.2+wheezy2_i386 + nginx-naxsi_1.2.1-2.2+wheezy2_i386 + passwd_1:4.1.5.1-1_i386 + perl-base_5.14.2-21+deb7u1_i386 + sed_4.2.1-10_i386 + sysvinit_2.88dsf-41+deb7u1_i386 + sysvinit-utils_2.88dsf-41+deb7u1_i386 + tar_1.26+dfsg-0.1_i386 + util-linux_2.20.1-5.3_i386 + xz-utils_5.1.1alpha+20120614-2_i386 + zlib1g_1:1.2.7.dfsg-13_i386 diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot5Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot5Test_gold new file mode 100644 index 00000000..c5297b7d --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot5Test_gold @@ -0,0 +1 @@ +ERROR: unable to filter: snapshot with name snap1 not found diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot6Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot6Test_gold new file mode 100644 index 00000000..dc77e3f2 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot6Test_gold @@ -0,0 +1,3 @@ +Loading packages (56121)... +Building indexes... +ERROR: unable to create snapshot: snapshot with name snap2 already exists diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_gold new file mode 100644 index 00000000..703595ce --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_gold @@ -0,0 +1,5 @@ +Loading packages (5550)... +Building indexes... + +Snapshot snap2 successfully filtered. +You can run 'aptly publish snapshot snap2' to publish snapshot as Debian repository. diff --git a/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_snapshot_show b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_snapshot_show new file mode 100644 index 00000000..b5e57038 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/FilterSnapshot7Test_snapshot_show @@ -0,0 +1,14 @@ +Name: snap2 +Description: Filtered 'snap1', query was: 'rsyslog (>= 7.4.4), $Architecture (i386)' +Number of packages: 10 +Packages: + init-system-helpers_1.18~bpo70+1_all + libestr0_0.1.9-1~bpo70+1_i386 + libjson-c2_0.11-3~bpo7+1_i386 + liblogging-stdlog0_1.0.4-1~bpo70+1_i386 + rsyslog_7.6.3-2~bpo70+1_i386 + init-system-helpers_1.18~bpo70+1_source + json-c_0.11-3~bpo7+1_source + libestr_0.1.9-1~bpo70+1_source + liblogging_1.0.4-1~bpo70+1_source + rsyslog_7.6.3-2~bpo70+1_source diff --git a/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot1Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot1Test_gold new file mode 100644 index 00000000..52405682 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot1Test_gold @@ -0,0 +1,4043 @@ + +aolserver4-dev_4.5.1-15.1_i386 +apache2-prefork-dev_2.2.22-13+deb7u1_i386 +apache2-threaded-dev_2.2.22-13+deb7u1_i386 +apcalc-dev_2.12.4.4-3_i386 +aplus-fsf-dev_4.22.1-6_i386 +aroarfw-dev_0.1~beta4-5_all +asterisk-dev_1:1.8.13.1~dfsg1-3+deb7u3_all +atfs-dev_1.4pl6-11_i386 +audacious-dev_3.2.4-1_i386 +autotools-dev_20120608.1_all +binutils-dev_2.22-8_i386 +biosquid-dev_1.9g+cvs20050121-2_i386 +bitlbee-dev_3.0.5-1.2_all +blacs-pvm-dev_1.1-21_i386 +blends-dev_0.6.16.2_all +blktap-dev_2.0.90-1_i386 +blt-dev_2.4z-4.2_i386 +boinc-dev_7.0.27+dfsg-5_i386 +boolstuff-dev_0.1.12-3_i386 +cairo-dock-dev_3.0.0-2+deb7u1_i386 +cernlib-base-dev_20061220+dfsg3-2_all +cernlib-core-dev_20061220+dfsg3-2_all +chipmunk-dev_5.3.4-1_i386 +cimg-dev_1.4.9-2_all +clearsilver-dev_0.10.5-1.3_i386 +cli-common-dev_0.8.2_all +clinica-dev_0.2.1~dfsg-1_i386 +clisp-dev_1:2.49-8.1_i386 +cluster-glue-dev_1.0.9+hg2665-1_i386 +codeblocks-dev_10.05-2.1_i386 +coinor-libcbc-dev_2.5.0-3_i386 +coinor-libcgl-dev_0.55.0-1.1_i386 +coinor-libclp-dev_1.12.0-2.1_i386 +coinor-libcoinutils-dev_2.6.4-3_i386 +coinor-libdylp-dev_1.6.0-1.1_i386 +coinor-libflopc++-dev_1.0.6-3.1_i386 +coinor-libipopt-dev_3.10.2-1.1_i386 +coinor-libosi-dev_0.103.0-1_i386 +coinor-libsymphony-dev_5.2.4-1.2_i386 +coinor-libvol-dev_1.1.7-1_i386 +collectd-dev_5.1.0-3_all +comerr-dev_2.1-1.42.5-1.1_i386 +condor-dev_7.8.2~dfsg.1-1+deb7u1_i386 +config-package-dev_4.13_all +connman-dev_1.0-1.1+wheezy1+b1_i386 +console-tools-dev_1:0.2.3dbs-70_i386 +coop-computing-tools-dev_3.5.1-2_i386 +corosync-dev_1.4.2-3_i386 +courier-authlib-dev_0.63.0-6+b1_i386 +crtmpserver-dev_1.0~dfsg-3_i386 +ctapi-dev_1.1_all +ctn-dev_3.0.6-13+b2_i386 +cyrus-dev_2.4.16-4+deb7u1_all +dcap-dev_2.47.6-2_i386 +dico-dev_2.1-3+b2_i386 +dictionaries-common-dev_1.12.11_all +dietlibc-dev_0.33~cvs20120325-4_i386 +dolfin-dev_1.0.0-7_all +dovecot-dev_1:2.1.7-7_i386 +dpkg-dev_1.16.12_all +drac-dev_1.12-7.2_i386 +drizzle-plugin-dev_1:7.1.36-stable-1_i386 +dssi-dev_1.1.1~dfsg0-1_all +e2fslibs-dev_1.42.5-1.1_i386 +emerillon-dev_0.1.90-1_i386 +eog-dev_3.4.2-1+build1_all +epiphany-browser-dev_3.4.2-2.1_i386 +erlang-dev_1:15.b.1-dfsg-4+deb7u1_i386 +erlang-esdl-dev_1.2-2_all +etl-dev_0.04.15-1_i386 +evolution-data-server-dev_3.4.4-3_i386 +evolution-dev_3.4.4-3_i386 +exim4-dev_4.80-7_i386 +expect-dev_5.45-2_i386 +expeyes-firmware-dev_2.0.0-3_all +extremetuxracer-gimp-dev_0.4-5_all +falconpl-dev_0.9.6.9-git20120606-2_i386 +fatrat-dev_1.1.3-5_all +fcitx-libs-dev_1:4.2.4.1-7_i386 +fenix-dev_0.92a.dfsg1-9_all +festival-dev_1:2.1~release-5.1_i386 +fftw-dev_2.1.5-1_i386 +finch-dev_2.10.9-1~deb7u1_all +firebird-dev_2.5.2.26540.ds4-1~deb7u1_i386 +flite1-dev_1.4-release-6_i386 +flow-tools-dev_1:0.68-12.1+b1_i386 +fosfat-dev_0.4.0-3_i386 +freeglut3-dev_2.6.0-4_i386 +freetds-dev_0.91-2+deb7u1_i386 +frei0r-plugins-dev_1.1.22git20091109-1.2_i386 +ftgl-dev_2.1.3~rc5-4_all +ftplib-dev_3.1-1-9_i386 +gambas3-dev_3.1.1-2+b1_i386 +gap-dev_4r4p12-2_i386 +gauche-dev_0.9.1-5.1_i386 +gcc-4.6-plugin-dev_4.6.3-14_i386 +gcc-4.7-plugin-dev_4.7.2-5_i386 +gcin-dev_2.7.6.1+dfsg-1_all +gedit-dev_3.4.2-1_all +gem-dev_1:0.93.3-5_all +genius-dev_1.0.14-1_i386 +gfxboot-dev_4.5.0-3_i386 +giblib-dev_1.2.4-8_i386 +glabels-dev_3.0.0-3+b1_i386 +glee-dev_5.4.0-1_i386 +gmpc-dev_11.8.16-6_i386 +gnash-dev_0.8.11~git20120629-1+deb7u1_i386 +gnome-control-center-dev_1:3.4.3.1-2_all +gnome-settings-daemon-dev_3.4.2+git20121218.7c1322-3+deb7u3_i386 +gnome-video-effects-dev_0.4.0-1_all +gnumach-dev_2:1.3.99.dfsg.git20120610-1_i386 +gnunet-dev_0.9.3-7_i386 +gnunet-gtk-dev_0.9.3-1_i386 +gnuradio-dev_3.5.3.2-1_i386 +gosa-dev_2.7.4-4.3~deb7u1_all +gpe-ownerinfo-dev_0.28-3_i386 +gpsim-dev_0.26.1-2.1_i386 +graphviz-dev_2.26.3-14+deb7u1_all +grass-dev_6.4.2-2_i386 +gridengine-drmaa-dev_6.2u5-7.1_i386 +gromacs-dev_4.5.5-2_i386 +gsettings-desktop-schemas-dev_3.4.2-3_i386 +gthumb-dev_3:3.0.1-2_i386 +guile-1.6-dev_1.6.8-10.3_i386 +guile-1.8-dev_1.8.8+1-8_i386 +guile-2.0-dev_2.0.5+1-3_i386 +guile-cairo-dev_1.4.0-3_i386 +heartbeat-dev_1:3.0.5-3_i386 +heimdal-dev_1.6~git20120403+dfsg1-2_i386 +hime-dev_0.9.9+git20120619+dfsg-1_all +hybrid-dev_1:7.2.2.dfsg.2-10_all +icedove-dev_10.0.12-1_i386 +inn2-dev_2.5.3-3_i386 +inventor-dev_2.1.5-10-16_i386 +iproute-dev_20120521-3+b3_i386 +iptables-dev_1.4.14-3.1_i386 +irssi-dev_0.8.15-5_i386 +isc-dhcp-dev_4.2.2.dfsg.1-5+deb70u6_i386 +itcl3-dev_3.4.1-1_i386 +itk3-dev_3.3-4_i386 +ivtools-dev_1.2.10a1-1_i386 +kadu-dev_0.11.2-1_all +kannel-dev_1.4.3-2+b2_i386 +kde-workspace-dev_4:4.8.4-6_i386 +kdebase-workspace-dev_4:4.8.4-6_all +kdelibs5-dev_4:4.8.4-4_i386 +kdemultimedia-dev_4:4.8.4-2_i386 +kdepimlibs5-dev_4:4.8.4-2_i386 +kdevelop-dev_4:4.3.1-3+b1_i386 +kdevplatform-dev_1.3.1-2_i386 +kmymoney-dev_4.6.2-3.2_i386 +konwert-dev_1.8-11.2_all +lam4-dev_7.1.4-3_i386 +lesstif2-dev_1:0.95.2-1.1_i386 +lib3ds-dev_1.3.0-6_i386 +lib4store-dev_1.1.4-2_i386 +lib64bz2-dev_1.0.6-4_i386 +lib64expat1-dev_2.1.0-1+deb7u1_i386 +lib64ffi-dev_3.0.10-3_i386 +lib64ncurses5-dev_5.9-10_i386 +lib64readline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +lib64readline6-dev_6.2+dfsg-0.1_i386 +lib64z1-dev_1:1.2.7.dfsg-13_i386 +liba52-0.7.4-dev_0.7.4-16_i386 +libaa1-dev_1.4p5-40_i386 +libaac-tactics-ocaml-dev_0.2.pl2-7_i386 +libaacs-dev_0.4.0-1_i386 +libaal-dev_1.0.5-5.1_i386 +libabiword-2.9-dev_2.9.2+svn20120603-8_i386 +libaccountsservice-dev_0.6.21-8_i386 +libace-dev_6.0.3+dfsg-0.1_i386 +libace-flreactor-dev_6.0.3+dfsg-0.1_i386 +libace-foxreactor-dev_6.0.3+dfsg-0.1_i386 +libace-htbp-dev_6.0.3+dfsg-0.1_i386 +libace-inet-dev_6.0.3+dfsg-0.1_i386 +libace-inet-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-qtreactor-dev_6.0.3+dfsg-0.1_i386 +libace-rmcast-dev_6.0.3+dfsg-0.1_i386 +libace-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-tkreactor-dev_6.0.3+dfsg-0.1_i386 +libace-tmcast-dev_6.0.3+dfsg-0.1_i386 +libace-xtreactor-dev_6.0.3+dfsg-0.1_i386 +libacexml-dev_6.0.3+dfsg-0.1_i386 +libacl1-dev_2.2.51-8_i386 +libacpi-dev_0.2-4_i386 +libacr38ucontrol-dev_1.7.11-1_i386 +libadasockets4-dev_1.8.10-2_i386 +libaddresses-dev_0.4.7-1+b5_i386 +libaddressview-dev_0.4.7-1+b5_i386 +libadios-dev_1.3-11_i386 +libadminutil-dev_1.1.15-1_i386 +libadns1-dev_1.4-2_i386 +libadolc-dev_2.3.0-1_i386 +libadplug-dev_2.2.1+dfsg3-0.1_i386 +libafflib-dev_3.6.6-1.1+b1_i386 +libafrodite-0.12-dev_0.12.1-3_i386 +libafterimage-dev_2.2.11-7_i386 +libagg-dev_2.5+dfsg1-8_i386 +libagrep-ocaml-dev_1.0-11+b3_i386 +libahven3-dev_2.1-4_i386 +libaiksaurus-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaiksaurusgtk-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaio-dev_0.3.109-3_i386 +libakonadi-dev_1.7.2-3_i386 +libalberta2-dev_2.0.1-5_i386 +libaldmb1-dev_1:0.9.3-5.4_i386 +libalglib-dev_2.6.0-6_i386 +libalkimia-dev_4.3.2-1.1_i386 +liballeggl4-dev_2:4.4.2-2.1_i386 +liballegro4.2-dev_2:4.4.2-2.1_i386 +libalog0.4.1-base-dev_0.4.1-2_i386 +libalog0.4.1-full-dev_0.4.1-2_i386 +libalsa-ocaml-dev_0.2.1-1+b1_i386 +libalsaplayer-dev_0.99.80-5.1_i386 +libalure-dev_1.2-6_i386 +libalut-dev_1.1.0-3_i386 +libampsharp-cil-dev_2.0.4-2_all +libamu-dev_6.2+rc20110530-3_i386 +libanalitza-dev_4:4.8.4-2_i386 +libanet0.1-dev_0.1-3_i386 +libanjuta-dev_2:3.4.3-1_i386 +libann-dev_1.1.2+doc-3_i386 +libanthy-dev_9100h-16_i386 +libantlr-dev_2.7.7+dfsg-4_i386 +libantlr3c-dev_3.2-2_i386 +libao-dev_1.1.0-2_i386 +libao-ocaml-dev_0.2.0-1+b2_i386 +libaosd-dev_0.2.7-1_i386 +libapache2-mod-perl2-dev_2.0.7-3_all +libapertium3-3.1-0-dev_3.1.0-2_i386 +libapm-dev_3.2.2-14_i386 +libapol-dev_3.3.7-3_i386 +libapparmor-dev_2.7.103-4_i386 +libappindicator-dev_0.4.92-2_i386 +libappindicator0.1-cil-dev_0.4.92-2_all +libappindicator3-dev_0.4.92-2_i386 +libapq-postgresql3.2.0-dev_3.2.0-2_i386 +libapq3.2.0-dev_3.2.0-1_i386 +libapr-memcache-dev_0.7.0-1_i386 +libapr1-dev_1.4.6-3+deb7u1_i386 +libapreq2-dev_2.13-1+b2_i386 +libapron-dev_0.9.10-5.2_all +libapron-ocaml-dev_0.9.10-5.2+b3_i386 +libaprutil1-dev_1.4.1-3_i386 +libapt-pkg-dev_0.9.7.9+deb7u1_i386 +libaqbanking34-dev_5.0.24-3_i386 +libaqsis-dev_1.8.1-3_i386 +libarchive-dev_3.0.4-3+nmu1_i386 +libargtable2-dev_12-1_i386 +libarmadillo-dev_1:3.2.3+dfsg-1_i386 +libarpack++2-dev_2.3-2_i386 +libarpack2-dev_3.1.1-2.1_i386 +libart-2.0-dev_2.3.21-2_i386 +libart2.0-cil-dev_2.24.2-3_all +libasio-dev_1.4.1-3.2_all +libasis2010-dev_2010-5_i386 +libasm-dev_0.152-1+wheezy1_i386 +libasound2-dev_1.0.25-4_i386 +libaspell-dev_0.60.7~20110707-1_i386 +libass-dev_0.10.0-3_i386 +libassa3.5-5-dev_3.5.1-2_i386 +libassimp-dev_3.0~dfsg-1_i386 +libassuan-dev_2.0.3-1_i386 +libast2-dev_0.7-6+b1_i386 +libasyncns-dev_0.8-4_i386 +libatasmart-dev_0.19-1_i386 +libatd-ocaml-dev_1.0.1-1+b1_i386 +libatdgen-ocaml-dev_1.2.2-1+b1_i386 +libatk-bridge2.0-dev_2.5.3-2_i386 +libatk1.0-dev_2.4.0-2_i386 +libatkmm-1.6-dev_2.22.6-1_i386 +libatlas-base-dev_3.8.4-9+deb7u1_i386 +libatlas-cpp-0.6-dev_0.6.2-3_i386 +libatlas-dev_3.8.4-9+deb7u1_all +libatm1-dev_1:2.5.1-1.5_i386 +libatomic-ops-dev_7.2~alpha5+cvs20101124-1+deb7u1_i386 +libatomicparsley-dev_2.1.2-1_i386 +libatspi-dev_1.32.0-2_i386 +libatspi2.0-dev_2.5.3-2_i386 +libattica-dev_0.2.0-1_i386 +libattr1-dev_1:2.4.46-8_i386 +libaubio-dev_0.3.2-4.2+b1_i386 +libaudio-dev_1.9.3-5wheezy1_i386 +libaudiofile-dev_0.3.4-2_i386 +libaudiomask-dev_1.0-2_i386 +libaudit-dev_1:1.7.18-1.1_i386 +libaugeas-dev_0.10.0-1_i386 +libaunit2-dev_1.03-7_i386 +libautotrace-dev_0.31.1-16+b1_i386 +libautounit-dev_0.20.1-4_i386 +libavahi-cil-dev_0.6.19-4.2_all +libavahi-client-dev_0.6.31-2_i386 +libavahi-common-dev_0.6.31-2_i386 +libavahi-compat-libdnssd-dev_0.6.31-2_i386 +libavahi-core-dev_0.6.31-2_i386 +libavahi-glib-dev_0.6.31-2_i386 +libavahi-gobject-dev_0.6.31-2_i386 +libavahi-qt4-dev_0.6.31-2_i386 +libavahi-ui-cil-dev_0.6.19-4.2_all +libavahi-ui-dev_0.6.31-2_i386 +libavahi-ui-gtk3-dev_0.6.31-2_i386 +libavbin-dev_7-1.3_i386 +libavc1394-dev_0.5.4-2_i386 +libavcodec-dev_6:0.8.10-1_i386 +libavdevice-dev_6:0.8.10-1_i386 +libavfilter-dev_6:0.8.10-1_i386 +libavformat-dev_6:0.8.10-1_i386 +libavifile-0.7-dev_1:0.7.48~20090503.ds-13_i386 +libavl-dev_0.3.5-3_i386 +libavogadro-dev_1.0.3-5_i386 +libavutil-dev_6:0.8.10-1_i386 +libaws2.10.2-dev_2.10.2-4_i386 +libax25-dev_0.0.12-rc2+cvs20120204-2_i386 +libbabl-dev_0.1.10-1_i386 +libball1.4-dev_1.4.1+20111206-4_i386 +libballview1.4-dev_1.4.1+20111206-4_i386 +libbam-dev_0.1.18-1_i386 +libbamf-dev_0.2.118-1_i386 +libbamf3-dev_0.2.118-1_i386 +libbarry-dev_0.18.3-5_i386 +libbatteries-ocaml-dev_1.4.3-1_i386 +libbdd-dev_2.4-8_i386 +libbeecrypt-dev_4.2.1-4_i386 +libbenchmark-ocaml-dev_0.9-2+b3_i386 +libbfb0-dev_0.23-1.1_i386 +libbg1-dev_1.106-1_i386 +libbibutils-dev_4.12-5_i386 +libbin-prot-camlp4-dev_2.0.7-1_i386 +libbind-dev_1:9.8.4.dfsg.P1-6+nmu2+deb7u1_i386 +libbind4-dev_6.0-1_i386 +libbinio-dev_1.4+dfsg1-1_i386 +libbiniou-ocaml-dev_1.0.0-1+b1_i386 +libbio2jack0-dev_0.9-2.1_i386 +libbiococoa-dev_2.2.2-1+b2_i386 +libbiosig-dev_1.3.0-2_i386 +libbisho-common-dev_0.27.2+git20111122.9e68ef3d-1_i386 +libbison-dev_1:2.5.dfsg-2.1_i386 +libbitmask-dev_2.0-2_i386 +libbitstream-dev_1.0-1_all +libbitstring-ocaml-dev_2.0.2-3+b1_i386 +libbjack-ocaml-dev_0.1.3-5+b1_i386 +libblacs-mpi-dev_1.1-31_i386 +libblas-dev_1.2.20110419-5_i386 +libbliss-dev_0.72-4_i386 +libblitz0-dev_1:0.9-13_i386 +libblkid-dev_2.20.1-5.3_i386 +libblocksruntime-dev_0.1-1_i386 +libbluedevil-dev_1.9.2-1_i386 +libbluetooth-dev_4.99-2_i386 +libbluray-dev_1:0.2.2-1_i386 +libbml-dev_0.6.1-1_i386 +libbobcat-dev_3.01.00-1+b1_i386 +libbogl-dev_0.1.18-8+b1_i386 +libbognor-regis-dev_0.6.12+git20101007.02c25268-7_i386 +libbonobo2-dev_2.24.3-1_i386 +libbonoboui2-dev_2.24.3-1_i386 +libboo-cil-dev_0.9.5~git20110729.r1.202a430-2_all +libboost-all-dev_1.49.0.1_i386 +libboost-chrono-dev_1.49.0.1_i386 +libboost-chrono1.49-dev_1.49.0-3.2_i386 +libboost-date-time-dev_1.49.0.1_i386 +libboost-date-time1.49-dev_1.49.0-3.2_i386 +libboost-dev_1.49.0.1_i386 +libboost-filesystem-dev_1.49.0.1_i386 +libboost-filesystem1.49-dev_1.49.0-3.2_i386 +libboost-graph-dev_1.49.0.1_i386 +libboost-graph-parallel-dev_1.49.0.1_i386 +libboost-graph-parallel1.49-dev_1.49.0-3.2_i386 +libboost-graph1.49-dev_1.49.0-3.2_i386 +libboost-iostreams-dev_1.49.0.1_i386 +libboost-iostreams1.49-dev_1.49.0-3.2_i386 +libboost-locale-dev_1.49.0.1_i386 +libboost-locale1.49-dev_1.49.0-3.2_i386 +libboost-math-dev_1.49.0.1_i386 +libboost-math1.49-dev_1.49.0-3.2_i386 +libboost-mpi-dev_1.49.0.1_i386 +libboost-mpi-python-dev_1.49.0.1_i386 +libboost-mpi-python1.49-dev_1.49.0-3.2_i386 +libboost-mpi1.49-dev_1.49.0-3.2_i386 +libboost-program-options-dev_1.49.0.1_i386 +libboost-program-options1.49-dev_1.49.0-3.2_i386 +libboost-python-dev_1.49.0.1_i386 +libboost-python1.49-dev_1.49.0-3.2_i386 +libboost-random-dev_1.49.0.1_i386 +libboost-random1.49-dev_1.49.0-3.2_i386 +libboost-regex-dev_1.49.0.1_i386 +libboost-regex1.49-dev_1.49.0-3.2_i386 +libboost-serialization-dev_1.49.0.1_i386 +libboost-serialization1.49-dev_1.49.0-3.2_i386 +libboost-signals-dev_1.49.0.1_i386 +libboost-signals1.49-dev_1.49.0-3.2_i386 +libboost-system-dev_1.49.0.1_i386 +libboost-system1.49-dev_1.49.0-3.2_i386 +libboost-test-dev_1.49.0.1_i386 +libboost-test1.49-dev_1.49.0-3.2_i386 +libboost-thread-dev_1.49.0.1_i386 +libboost-thread1.49-dev_1.49.0-3.2_i386 +libboost-timer-dev_1.49.0.1_i386 +libboost-timer1.49-dev_1.49.0-3.2_i386 +libboost-wave-dev_1.49.0.1_i386 +libboost-wave1.49-dev_1.49.0-3.2_i386 +libboost1.49-all-dev_1.49.0-3.2_i386 +libboost1.49-dev_1.49.0-3.2_i386 +libbotan1.10-dev_1.10.5-1_i386 +libbox-dev_2.5-2_i386 +libbox2d-dev_2.0.1+dfsg1-1_i386 +libbpp-core-dev_2.0.3-1_i386 +libbpp-phyl-dev_2.0.3-1_i386 +libbpp-popgen-dev_2.0.3-1_i386 +libbpp-qt-dev_2.0.2-1_i386 +libbpp-raa-dev_2.0.3-1_i386 +libbpp-seq-dev_2.0.3-1_i386 +libbrahe-dev_1.3.2-3_i386 +libbrasero-media3-dev_3.4.1-4_i386 +libbrlapi-dev_4.4-10+deb7u1_i386 +libbs2b-dev_3.1.0+dfsg-2_i386 +libbsd-dev_0.4.2-1_i386 +libbse-dev_0.7.4-5_all +libbt-dev_0.70.1-13_i386 +libbtparse-dev_0.63-1_i386 +libbuffy-dev_1.7-1_i386 +libbulletml-dev_0.0.6-5_i386 +libburn-dev_1.2.2-2_i386 +libbuzztard-dev_0.5.0-4_i386 +libbz2-dev_1.0.6-4_i386 +libbz2-ocaml-dev_0.6.0-6+b2_i386 +libc-ares-dev_1.9.1-3_i386 +libc-client2007e-dev_8:2007f~dfsg-2_i386 +libc6-dev_2.13-38+deb7u1_i386 +libcableswig-dev_0.1.0+cvs20111009-1_i386 +libcaca-dev_0.99.beta18-1_i386 +libcairo-ocaml-dev_1:1.2.0-2+b1_i386 +libcairo2-dev_1.12.2-3_i386 +libcairomm-1.0-dev_1.10.0-1_i386 +libcal3d12-dev_0.11.0-4.1_i386 +libcalendar-ocaml-dev_2.03-1+b2_i386 +libcamel1.2-dev_3.4.4-3_i386 +libcameleon-ocaml-dev_1.9.21-2+b1_i386 +libcaml2html-ocaml-dev_1.4.1-3_i386 +libcamlimages-ocaml-dev_1:4.0.1-4+b2_i386 +libcamljava-ocaml-dev_0.3-1+b3_i386 +libcamltemplate-ocaml-dev_1.0.2-1+b2_i386 +libcamomile-ocaml-dev_0.8.4-2_i386 +libcanberra-dev_0.28-6_i386 +libcanberra-gtk-common-dev_0.28-6_all +libcanberra-gtk-dev_0.28-6_i386 +libcanberra-gtk3-dev_0.28-6_i386 +libcanlock2-dev_2b-6_i386 +libcanna1g-dev_3.7p3-11_i386 +libcap-dev_1:2.22-1.2_i386 +libcap-ng-dev_0.6.6-2_i386 +libcapi20-dev_1:3.25+dfsg1-3.3~deb7u1_i386 +libcapsinetwork-dev_0.3.0-7_i386 +libcaribou-dev_0.4.4-1_i386 +libcbf-dev_0.7.9.1-3_i386 +libccaudio2-dev_2.0.5-3_i386 +libccfits-dev_2.4-1_i386 +libcconv-dev_0.6.2-1_i386 +libccrtp-dev_2.0.3-4_i386 +libccs-dev_3.0.12-3.2+deb7u2_i386 +libccscript3-dev_1.1.7-2_i386 +libccss-dev_0.5.0-4_i386 +libcdaudio-dev_0.99.12p2-12_i386 +libcdb-dev_0.78_i386 +libcdd-dev_094b.dfsg-4.2_i386 +libcddb2-dev_1.3.2-3_i386 +libcdi-dev_1.5.4+dfsg.1-5_i386 +libcdio-cdda-dev_0.83-4_i386 +libcdio-dev_0.83-4_i386 +libcdio-paranoia-dev_0.83-4_i386 +libcdk5-dev_5.0.20060507-4_i386 +libcdparanoia-dev_3.10.2+debian-10.1_i386 +libcec-dev_1.6.2-1.1_i386 +libcegui-mk2-dev_0.7.6-2+b1_i386 +libcext-dev_6.1.1-2_i386 +libcf-ocaml-dev_0.10-3+b3_i386 +libcfg-dev_1.4.2-3_i386 +libcfitsio3-dev_3.300-2_i386 +libcgal-dev_4.0-5_i386 +libcgic-dev_2.05-3_i386 +libcgicc5-dev_3.2.9-3_i386 +libcgns-dev_3.1.3.4-1+b1_i386 +libcgroup-dev_0.38-1_i386 +libcgsi-gsoap-dev_1.3.5-1_i386 +libchamplain-0.12-dev_0.12.3-1_i386 +libchamplain-gtk-0.12-dev_0.12.3-1_i386 +libcharls-dev_1.0-2_i386 +libchasen-dev_2.4.5-6_i386 +libcheese-dev_3.4.2-2_i386 +libcheese-gtk-dev_3.4.2-2_i386 +libchewing3-dev_0.3.3-4_i386 +libchicken-dev_4.7.0-1_i386 +libchipcard-dev_5.0.3beta-3_i386 +libchise-dev_0.3.0-2+b1_i386 +libchm-dev_2:0.40a-2_i386 +libchromaprint-dev_0.6-2_i386 +libcib1-dev_1.1.7-1_i386 +libcitadel-dev_8.14-1_i386 +libcitygml0-dev_0.14+svn128-1+3p0p1+4_i386 +libck-connector-dev_0.4.5-3.1_i386 +libckyapplet1-dev_1.1.0-12_i386 +libclalsadrv-dev_2.0.0-3_all +libclam-dev_1.4.0-5.1_i386 +libclam-qtmonitors-dev_1.4.0-3.1_i386 +libclamav-dev_0.98.1+dfsg-1+deb7u3_i386 +libclang-common-dev_1:3.0-6.2_i386 +libclang-dev_1:3.0-6.2_i386 +libclanlib-dev_1.0~svn3827-3_i386 +libclassad-dev_7.8.2~dfsg.1-1+deb7u1_i386 +libclaw-application-dev_1.7.0-3_i386 +libclaw-configuration-file-dev_1.7.0-3_i386 +libclaw-dev_1.7.0-3_i386 +libclaw-dynamic-library-dev_1.7.0-3_i386 +libclaw-graphic-dev_1.7.0-3_i386 +libclaw-logger-dev_1.7.0-3_i386 +libclaw-net-dev_1.7.0-3_i386 +libclaw-tween-dev_1.7.0-3_i386 +libclaws-mail-dev_3.8.1-2_i386 +libclhep-dev_2.1.2.3-1_i386 +libcli-dev_1.9.6-1_i386 +libclippoly-dev_0.11-3_i386 +libclips-dev_6.24-3_i386 +libcliquer-dev_1.21-1_i386 +libcln-dev_1.3.2-1.2_i386 +libcloog-isl-dev_0.17.0-3_i386 +libcloog-ppl-dev_0.15.11-4_i386 +libclthreads-dev_2.4.0-4_i386 +libclucene-dev_0.9.21b-2+b1_i386 +libclustalo-dev_1.1.0-1_i386 +libcluster-glue-dev_1.0.9+hg2665-1_all +libclutter-1.0-dev_1.10.8-2_i386 +libclutter-cil-dev_1.0.0~alpha3~git20090817.r1.349dba6-8_all +libclutter-gst-dev_1.5.4-1+build0_i386 +libclutter-gtk-1.0-dev_1.2.0-2_i386 +libclutter-imcontext-0.1-dev_0.1.4-3_i386 +libcluttergesture-dev_0.0.2.1-7_i386 +libclxclient-dev_3.6.1-6_i386 +libcman-dev_3.0.12-3.2+deb7u2_i386 +libcminpack-dev_1.2.2-1_i386 +libcmis-dev_0.1.0-1+b1_i386 +libcmor-dev_2.8.0-2+b1_i386 +libcmph-dev_0.9-1_i386 +libcneartree-dev_3.1.1-1_i386 +libcnf-dev_4.0-2_i386 +libcob1-dev_1.1-1_i386 +libcogl-dev_1.10.2-7_i386 +libcogl-pango-dev_1.10.2-7_i386 +libcoin60-dev_3.1.3-2.2_i386 +libcojets2-dev_20061220+dfsg3-2_i386 +libcollectdclient-dev_5.1.0-3_i386 +libcollection-dev_0.1.3-2_i386 +libcolorblind-dev_0.0.1-1_i386 +libcolord-dev_0.1.21-1_i386 +libcolord-gtk-dev_0.1.21-1_i386 +libcolorhug-dev_0.1.10-1_i386 +libcomedi-dev_0.10.0-3_i386 +libcommoncpp2-dev_1.8.1-5_i386 +libcompfaceg1-dev_1:1.5.2-5_i386 +libconcord-dev_0.24-1.1_i386 +libconfdb-dev_1.4.2-3_i386 +libconfig++-dev_1.4.8-5_i386 +libconfig++8-dev_1.4.8-5_i386 +libconfig-dev_1.4.8-5_i386 +libconfig-file-ocaml-dev_1.1-1_i386 +libconfig8-dev_1.4.8-5_i386 +libconfuse-dev_2.7-4_i386 +libcontactsdb-dev_0.5-8_i386 +libcoq-ocaml-dev_8.3.pl4+dfsg-2_i386 +libcore-ocaml-dev_107.01-5_i386 +libcorelinux-dev_0.4.32-7.3_i386 +libcoroipcc-dev_1.4.2-3_i386 +libcoroipcs-dev_1.4.2-3_i386 +libcorosync-dev_1.4.2-3_all +libcos4-dev_4.1.6-2_i386 +libcothreads-ocaml-dev_0.10-3+b3_i386 +libcoyotl-dev_3.1.0-5_i386 +libcpg-dev_1.4.2-3_i386 +libcpl-dev_6.1.1-2_i386 +libcppcutter-dev_1.1.7-1.2_i386 +libcppunit-dev_1.12.1-4_i386 +libcppunit-subunit-dev_0.0.8+bzr176-1_i386 +libcpputest-dev_3.1-2_i386 +libcpufreq-dev_008-1_i386 +libcpuset-dev_1.0-3_i386 +libcqrlib2-dev_1.1.2-1_i386 +libcr-dev_0.8.5-2_i386 +libcrack2-dev_2.8.19-3_i386 +libcreal-ocaml-dev_0.7-6+b3_i386 +libcrmcluster1-dev_1.1.7-1_i386 +libcrmcommon2-dev_1.1.7-1_i386 +libcroco3-dev_0.6.6-2_i386 +libcry-ocaml-dev_0.2.2-1+b1_i386 +libcryptgps-ocaml-dev_0.2.1-7+b3_i386 +libcrypto++-dev_5.6.1-6_i386 +libcryptokit-ocaml-dev_1.5-1_i386 +libcryptsetup-dev_2:1.4.3-4_i386 +libcryptui-dev_3.2.2-1_i386 +libcrystalhd-dev_1:0.0~git20110715.fdd2f19-9_i386 +libcsfml-dev_1.6-1_i386 +libcsnd-dev_1:5.17.11~dfsg-3_all +libcsoap-dev_1.1.0-17.1_i386 +libcsound64-dev_1:5.17.11~dfsg-3_all +libcsoundac-dev_1:5.17.11~dfsg-3_all +libcsv-ocaml-dev_1.2.2-1+b1_i386 +libctapimkt0-dev_1.0.1-1.1_i386 +libctdb-dev_1.12+git20120201-4_i386 +libctemplate-dev_2.2-3_i386 +libctl-dev_3.1.0-5_i386 +libctpl-dev_0.3.3.dfsg-2_i386 +libcuba3-dev_3.0+20111124-2_i386 +libcudf-dev_0.6.2-1_i386 +libcudf-ocaml-dev_0.6.2-1_i386 +libcue-dev_1.4.0-1_i386 +libcunit1-dev_2.1-0.dfsg-10_i386 +libcunit1-ncurses-dev_2.1-0.dfsg-10_i386 +libcups2-dev_1.5.3-5+deb7u1_i386 +libcupscgi1-dev_1.5.3-5+deb7u1_i386 +libcupsdriver1-dev_1.5.3-5+deb7u1_i386 +libcupsfilters-dev_1.0.18-2.1+deb7u1_i386 +libcupsimage2-dev_1.5.3-5+deb7u1_i386 +libcupsmime1-dev_1.5.3-5+deb7u1_i386 +libcupsppdc1-dev_1.5.3-5+deb7u1_i386 +libcupt2-dev_2.5.9_i386 +libcurl-ocaml-dev_0.5.3-2+b1_i386 +libcurl4-gnutls-dev_7.26.0-1+wheezy9_i386 +libcurl4-nss-dev_7.26.0-1+wheezy9_i386 +libcurl4-openssl-dev_7.26.0-1+wheezy9_i386 +libcurses-ocaml-dev_1.0.3-2_i386 +libcutter-dev_1.1.7-1.2_i386 +libcv-dev_2.3.1-11_i386 +libcvaux-dev_2.3.1-11_i386 +libcvc3-dev_2.4.1-4_i386 +libcvector2-dev_1.0.3-1_i386 +libcvm1-dev_0.96-1+b1_i386 +libcw3-dev_3.0.2-1_i386 +libcwidget-dev_0.5.16-3.4_i386 +libcwiid-dev_0.6.00+svn201-3+b1_i386 +libcwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libcxgb3-dev_1.3.1-1_i386 +libcxxtools-dev_2.1.1-1_i386 +libdacs-dev_1.4.27b-2_i386 +libdaemon-dev_0.14-2_i386 +libdancer-xml0-dev_0.8.2.1-3_i386 +libdap-dev_3.11.1-11_i386 +libdapl-dev_2.0.19-1.1_i386 +libdaq-dev_0.6.2-2_i386 +libdar-dev_2.4.5.debian.1-1_i386 +libdatrie-dev_0.2.5-3_i386 +libdawgdic-dev_0.4.3-1_all +libdb++-dev_5.1.6_i386 +libdb-dev_5.1.6_i386 +libdb-java-dev_5.1.6_i386 +libdb-sql-dev_5.1.6_i386 +libdb4o-cil-dev_8.0.184.15484+dfsg-2_all +libdb5.1++-dev_5.1.29-5_i386 +libdb5.1-dev_5.1.29-5_i386 +libdb5.1-java-dev_5.1.29-5_i386 +libdb5.1-sql-dev_5.1.29-5_i386 +libdb5.1-stl-dev_5.1.29-5_i386 +libdballe-dev_5.18-1_i386 +libdballef-dev_5.18-1_i386 +libdbaudiolib0-dev_0.9.8-6.2_i386 +libdbi-dev_0.8.4-6_i386 +libdbus-1-dev_1.6.8-1+deb7u1_i386 +libdbus-c++-dev_0.9.0-6_i386 +libdbus-glib-1-dev_0.100.2-1_i386 +libdbus-glib1.0-cil-dev_0.5.0-4_all +libdbus-ocaml-dev_0.29-1+b3_i386 +libdbus1.0-cil-dev_0.7.0-5_all +libdbusada0.2-dev_0.2-2_i386 +libdbusmenu-glib-dev_0.6.2-1_i386 +libdbusmenu-gtk-dev_0.6.2-1_i386 +libdbusmenu-gtk3-dev_0.6.2-1_i386 +libdbusmenu-jsonloader-dev_0.6.2-1_i386 +libdbusmenu-qt-dev_0.9.0-1_i386 +libdc-dev_0.3.24~svn3121-2_i386 +libdc1394-22-dev_2.2.0-2_i386 +libdca-dev_0.0.5-5_i386 +libdcerpc-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcerpc-server-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcmtk2-dev_3.6.0-12_i386 +libdconf-dbus-1-dev_0.12.1-3_i386 +libdconf-dev_0.12.1-3_i386 +libddccontrol-dev_0.4.2-10_i386 +libdds-dev_2.1.2+ddd105-1_i386 +libdebconf-kde-dev_0.2-2_i386 +libdebconfclient0-dev_0.182_i386 +libdebian-installer4-dev_0.87_i386 +libdebug0-dev_0.4.4-1.1_i386 +libdecodeqr-dev_0.9.3-6.2_i386 +libdee-dev_1.0.10-3_i386 +libderiving-ocaml-dev_0.1.1a-3+b1_i386 +libderiving-ocsigen-ocaml-dev_0.3c-1_i386 +libdesktop-agnostic-dev_0.3.92+dfsg-1_i386 +libdessert0.87-dev_0.87.2-1_i386 +libdevhelp-dev_3.4.1-1_i386 +libdevil-dev_1.7.8-6.1+b1_i386 +libdevmapper-dev_2:1.02.74-8_i386 +libdhash-dev_0.1.3-2_i386 +libdiagnostics-dev_0.3.3-1.3_i386 +libdianewcanvas2-dev_0.6.10-5.4_i386 +libdieharder-dev_3.31.1-4_i386 +libdiet-admin2.8-dev_2.8.0-1+b1_i386 +libdiet-client2.8-dev_2.8.0-1+b1_i386 +libdiet-dagda2.8-dev_2.8.0-1+b1_i386 +libdiet-sed2.8-dev_2.8.0-1+b1_i386 +libdime-dev_0.20030921-2_all +libdirac-dev_1.0.2-6_i386 +libdirectfb-dev_1.2.10.0-5_i386 +libdisasm-dev_0.23-5_i386 +libdiscid0-dev_0.2.2-3_i386 +libdiscover-dev_2.1.2-5.2_i386 +libdispatch-dev_0~svn197-3.1_i386 +libdisplaymigration0-dev_0.28-10_i386 +libdistorm64-dev_1.7.30-1_i386 +libdivecomputer-dev_0.1.0-3_i386 +libdjconsole-dev_0.1.3-1_i386 +libdjvulibre-dev_3.5.25.3-1_i386 +libdkim-dev_1:1.0.21-3_i386 +libdlm-dev_3.0.12-3.2+deb7u2_i386 +libdlmcontrol-dev_3.0.12-3.2+deb7u2_i386 +libdlrestrictions-dev_0.15.3_i386 +libdm0-dev_2.2.10-1_i386 +libdmalloc-dev_5.5.2-5_i386 +libdmapsharing-3.0-dev_2.9.15-1_i386 +libdmraid-dev_1.0.0.rc16-4.2_i386 +libdmtcpaware-dev_1.2.5-1_i386 +libdmtx-dev_0.7.2-2+build1_i386 +libdmx-dev_1:1.1.2-1+deb7u1_i386 +libdnet-dev_2.60_i386 +libdockapp-dev_1:0.5.0-3_i386 +libdolfin1.0-dev_1.0.0-7_i386 +libdoodle-dev_0.7.0-5_i386 +libdose2-ocaml-dev_1.4.2-4+b3_i386 +libdose3-ocaml-dev_3.0.2-3_i386 +libdotconf-dev_1.0.13-3_i386 +libdpkg-dev_1.16.12_i386 +libdpm-dev_1.8.2-1+b2_i386 +libdrawtk-dev_2.0-2_i386 +libdrizzle-dev_1:7.1.36-stable-1_all +libdrizzledmessage-dev_1:7.1.36-stable-1_i386 +libdrm-dev_2.4.40-1~deb7u2_i386 +libdrumstick-dev_0.5.0-3_i386 +libdsdp-dev_5.8-9.1_i386 +libdshconfig1-dev_0.20.13-1_i386 +libdspam7-dev_3.10.1+dfsg-11_i386 +libdssi-ocaml-dev_0.1.0-1+b1_i386 +libdssialsacompat-dev_1.0.8a-1_i386 +libdtools-ocaml-dev_0.3.0-1_i386 +libdts-dev_0.0.5-5_i386 +libdumb1-dev_1:0.9.3-5.4_i386 +libdumbnet-dev_1.12-3.1_i386 +libdune-common-dev_2.2.0-1_i386 +libdune-geometry-dev_2.2.0-1_i386 +libdune-grid-dev_2.2.0-1_i386 +libdune-istl-dev_2.2.0-1_all +libdune-localfunctions-dev_2.2.0-1_all +libduo-dev_1.8-1_i386 +libduppy-ocaml-dev_0.4.2-1+b2_i386 +libdv4-dev_1.0.0-6_i386 +libdvb-dev_0.5.5.1-5.1_i386 +libdvbcsa-dev_1.1.0-2_i386 +libdvbpsi-dev_0.2.2-1_i386 +libdvdnav-dev_4.2.0+20120524-2_i386 +libdvdread-dev_4.2.0+20120521-2_i386 +libdw-dev_0.152-1+wheezy1_i386 +libdwarf-dev_20120410-2_i386 +libdx4-dev_1:4.4.4-4+b2_i386 +libdxflib-dev_2.2.0.0-8_i386 +libdynamite-dev_0.1.1-2_i386 +libeasy-format-ocaml-dev_1.0.0-1+b2_i386 +libeb16-dev_4.4.3-6_i386 +libebackend1.2-dev_3.4.4-3_i386 +libebml-dev_1.2.2-2_i386 +libebook1.2-dev_3.4.4-3_i386 +libecal1.2-dev_3.4.4-3_i386 +libecasoundc-dev_2.9.0-1_i386 +libecasoundc2.2-dev_2.9.0-1_all +libechonest-dev_1.2.1-1_i386 +libecm-dev_6.4.2-1_i386 +libecore-dev_1.2.0-2_i386 +libecpg-dev_9.1.13-0wheezy1_i386 +libecryptfs-dev_99-1_i386 +libedac-dev_0.18-1_i386 +libedata-book1.2-dev_3.4.4-3_i386 +libedata-cal1.2-dev_3.4.4-3_i386 +libedataserver1.2-dev_3.4.4-3_i386 +libedataserverui-3.0-dev_3.4.4-3_i386 +libedbus-dev_1.2.0-1_i386 +libedit-dev_2.11-20080614-5_i386 +libeditline-dev_1.12-6_i386 +libedje-dev_1.2.0-1_i386 +libee-dev_0.4.1-1_i386 +libeegdev-dev_0.2-3_i386 +libeet-dev_1.6.0-1_i386 +libefreet-dev_1.2.0-1_i386 +libegl1-mesa-dev_8.0.5-4+deb7u2_i386 +libeigen2-dev_2.0.17-1_i386 +libeigen3-dev_3.1.0-1_i386 +libeina-dev_1.2.0-2_i386 +libelektra-cpp-dev_0.7.1-1_i386 +libelektra-dev_0.7.1-1_i386 +libelektratools-dev_0.7.1-1_i386 +libelemental-dev_1.2.0-8_i386 +libelementary-dev_0.7.0.55225-1_i386 +libelf-dev_0.152-1+wheezy1_i386 +libelfg0-dev_0.8.13-3_i386 +libeliom-ocaml-dev_2.2.2-1_i386 +libelk0-dev_3.99.8-2_i386 +libelmer-dev_6.1.0.svn.5396.dfsg2-2_i386 +libembryo-dev_1.2.0-1_i386 +libemos-dev_000382+dfsg-2_i386 +libenca-dev_1.13-4_i386 +libenchant-dev_1.6.0-7_i386 +libenet-dev_1.3.3-2_i386 +libepc-dev_0.4.4-1_i386 +libepc-ui-dev_0.4.4-1_i386 +libepr-api2-dev_2.2-2_all +libepsilon-dev_0.9.1-2_i386 +libept-dev_1.0.9_i386 +libepub-dev_0.2.1-2+b1_i386 +liberis-1.3-dev_1.3.19-5_i386 +liberuby-dev_1.0.5-2.1_i386 +libescpr-dev_1.1.1-2_i386 +libesd0-dev_0.2.41-10+b1_i386 +libesmtp-dev_1.0.6-1+b1_i386 +libespeak-dev_1.46.02-2_i386 +libestools2.1-dev_1:2.1~release-5_i386 +libestr-dev_0.1.1-2_i386 +libethos-dev_0.2.2-3_i386 +libethos-ui-dev_0.2.2-3_i386 +libetpan-dev_1.0-5_i386 +libetsf-io-dev_1.0.3-4+b1_i386 +libeurodec1-dev_20061220+dfsg3-2_i386 +libev-dev_1:4.11-1_i386 +libev-libevent-dev_1:4.11-1_all +libeval0-dev_0.29.6-2_i386 +libevas-dev_1.2.0-2_i386 +libevd-0.1-dev_0.1.20-2_i386 +libevent-dev_2.0.19-stable-3_i386 +libeventdb-dev_0.90-5_i386 +libevince-dev_3.4.0-3.1_i386 +libevocosm-dev_4.0.2-2.1_i386 +libevs-dev_1.4.2-3_i386 +libevtlog-dev_0.2.12-5_i386 +libewf-dev_20100226-1+b1_i386 +libexchangemapi-1.0-dev_3.4.4-1_i386 +libexempi-dev_2.2.0-1_i386 +libexif-dev_0.6.20-3_i386 +libexif-gtk-dev_0.3.5-5_i386 +libexiv2-dev_0.23-1_i386 +libexo-1-dev_0.6.2-5_i386 +libexodusii-dev_5.14.dfsg.1-2+b1_i386 +libexosip2-dev_3.6.0-4_i386 +libexpat-ocaml-dev_0.9.1+debian1-7+b2_i386 +libexpat1-dev_2.1.0-1+deb7u1_i386 +libexpect-ocaml-dev_0.0.2-1+b6_i386 +libexplain-dev_0.52.D002-1_i386 +libextlib-ocaml-dev_1.5.2-1+b1_i386 +libextractor-dev_1:0.6.3-5_i386 +libextractor-java-dev_0.6.0-6_i386 +libexttextcat-dev_3.2.0-2_i386 +libextunix-ocaml-dev_0.0.5-2_i386 +libeztrace-dev_0.7-2-4_i386 +libf2c2-dev_20090411-2_i386 +libfaad-dev_2.7-8_i386 +libfaad-ocaml-dev_0.3.0-1+b1_i386 +libfacile-ocaml-dev_1.1-8+b1_i386 +libfaifa-dev_0.2~svn82-1_i386 +libfakekey-dev_0.1-7_i386 +libfam-dev_2.7.0-17_i386 +libfann-dev_2.1.0~beta~dfsg-8_i386 +libfarstream-0.1-dev_0.1.2-1_i386 +libfastjet-dev_3.0.2+dfsg-2_i386 +libfastjet-fortran-dev_3.0.2+dfsg-2_i386 +libfastjetplugins-dev_3.0.2+dfsg-2_i386 +libfastjettools-dev_3.0.2+dfsg-2_i386 +libfauhdli-dev_20110812-1_i386 +libfcgi-dev_2.4.0-8.1_i386 +libfdt-dev_1.3.0-4_i386 +libfence-dev_3.0.12-3.2+deb7u2_i386 +libffado-dev_2.0.99+svn2171-2_i386 +libffcall1-dev_1.10+cvs20100619-2_i386 +libffi-dev_3.0.10-3_i386 +libffindex0-dev_0.9.6.1-1_i386 +libffmpegthumbnailer-dev_2.0.7-2_i386 +libffms2-dev_2.17-1_i386 +libfftw3-dev_3.3.2-3.1_i386 +libfftw3-mpi-dev_3.3.2-3.1_i386 +libfields-camlp4-dev_107.01-1+b2_i386 +libfileutils-ocaml-dev_0.4.2-1+b2_i386 +libfindlib-ocaml-dev_1.3.1-1_i386 +libfiredns-dev_0.9.12+dfsg-3_i386 +libfirestring-dev_0.9.12-8_i386 +libfishsound1-dev_1.0.0-1.1_i386 +libfiu-dev_0.90-3_i386 +libfixposix-dev_20110316.git47f17f7-1_i386 +libfko0-dev_2.0.0rc2-2+deb7u2_i386 +libflac++-dev_1.2.1-6_i386 +libflac-dev_1.2.1-6_i386 +libflac-ocaml-dev_0.1.1-1_i386 +libflake-dev_0.11-2_i386 +libflann-dev_1.7.1-4_i386 +libflatzebra-dev_0.1.5-4+b1_i386 +libflickrnet-cil-dev_1:2.2.0-4_all +libflorist2011-dev_2011-1_i386 +libflowcanvas-dev_0.7.1+dfsg0-0.2_i386 +libfltk1.1-dev_1.1.10-14_i386 +libfltk1.3-dev_1.3.0-8_i386 +libfluidsynth-dev_1.1.5-2_i386 +libfm-dev_0.1.17-2.1_i386 +libfolia1-dev_0.9-2_i386 +libfolks-dev_0.6.9-1+b1_i386 +libfolks-eds-dev_0.6.9-1+b1_i386 +libfolks-telepathy-dev_0.6.9-1+b1_i386 +libfontconfig1-dev_2.9.0-7.1_i386 +libfontenc-dev_1:1.1.1-1_i386 +libfontforge-dev_0.0.20120101+git-2_i386 +libforms-dev_1.0.93sp1-2_i386 +libformsgl-dev_1.0.93sp1-2_i386 +libfox-1.6-dev_1.6.45-1_i386 +libfprint-dev_1:0.4.0-4-gdfff16f-4_i386 +libfreecell-solver-dev_3.12.0-1_i386 +libfreefem++-dev_3.19.1-1_i386 +libfreefem-dev_3.5.8-5_i386 +libfreehdl0-dev_0.0.7-1.1_i386 +libfreeimage-dev_3.15.1-1+b1_i386 +libfreeipmi-dev_1.1.5-3_i386 +libfreenect-dev_1:0.1.2+dfsg-6_i386 +libfreeradius-dev_2.1.12+dfsg-1.2_i386 +libfreerdp-dev_1.0.1-1.1+deb7u3_i386 +libfreetype6-dev_2.4.9-1.1_i386 +libfreexl-dev_1.0.0b-1_i386 +libfribidi-dev_0.19.2-3_i386 +libfs-dev_2:1.0.4-1+deb7u1_i386 +libfso-glib-dev_2012.05.24.1-1.1_i386 +libfsobasics-dev_0.11.0-1.1_i386 +libfsoframework-dev_0.11.0-1.1_i386 +libfsoresource-dev_0.11.0-1.1_i386 +libfsosystem-dev_0.11.0-1_i386 +libfsotransport-dev_0.11.1-2.1_i386 +libfsplib-dev_0.11-2_i386 +libfstrcmp-dev_0.4.D001-1+deb7u1_i386 +libftdi-dev_0.20-1+b1_i386 +libftdipp-dev_0.20-1+b1_i386 +libftgl-dev_2.1.3~rc5-4_i386 +libfuntools-dev_1.4.4-3_i386 +libfuse-dev_2.9.0-2+deb7u1_i386 +libfuzzy-dev_2.7-2_i386 +libfxt-dev_0.2.6-2_i386 +libg15-dev_1.2.7-2_i386 +libg15daemon-client-dev_1.9.5.3-8.2_i386 +libg15render-dev_1.3.0~svn316-2.2_i386 +libg2-dev_0.72-2.1_i386 +libg3d-dev_0.0.8-17_i386 +libga-dev_2.4.7-3_i386 +libgadap-dev_2.0-1_i386 +libgadu-dev_1:1.11.2-1+deb7u1_i386 +libgail-3-dev_3.4.2-7_i386 +libgail-dev_2.24.10-2_i386 +libgalax-ocaml-dev_1.1-10+b3_i386 +libgambc4-dev_4.2.8-1.1_i386 +libgamin-dev_0.1.10-4.1_i386 +libgammu-dev_1.31.90-1+b1_i386 +libganglia1-dev_3.3.8-1+nmu1_i386 +libganv-dev_0~svn4468~dfsg0-1_i386 +libgarcon-1-0-dev_0.1.12-1_i386 +libgarmin-dev_0~svn320-3_i386 +libgatos-dev_0.0.5-19_i386 +libgavl-dev_1.4.0-1_i386 +libgavl-ocaml-dev_0.1.4-1+b1_i386 +libgbm-dev_8.0.5-4+deb7u2_i386 +libgc-dev_1:7.1-9.1_i386 +libgcal-dev_0.9.6-3_i386 +libgccxml-dev_0.9.0+cvs20120420-4_i386 +libgcgi-dev_0.9.5.dfsg-7_i386 +libgcj12-dev_4.6.3-1_i386 +libgcj13-dev_4.7.2-3_i386 +libgck-1-dev_3.4.1-3_i386 +libgconf-bridge-dev_0.1-2.2_i386 +libgconf2-dev_3.2.5-1+build1_i386 +libgconf2.0-cil-dev_2.24.2-3_all +libgconfmm-2.6-dev_2.28.0-1_i386 +libgcr-3-dev_3.4.1-3_i386 +libgcroots-dev_0.8.5-2.1_i386 +libgcrypt11-dev_1.5.0-5+deb7u1_i386 +libgctp-dev_1.0-1_i386 +libgd-gd2-noxpm-ocaml-dev_1.0~alpha5-5_i386 +libgd2-noxpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgda-5.0-dev_5.0.3-2_i386 +libgdal-dev_1.9.0-3.1_i386 +libgdal1-dev_1.9.0-3.1_all +libgdata-cil-dev_2.1.0.0-1_all +libgdata-dev_0.12.0-1_i386 +libgdb-dev_7.4.1+dfsg-0.1_i386 +libgdbm-dev_1.8.3-11_i386 +libgdchart-gd2-noxpm-dev_0.11.5-7+b1_i386 +libgdchart-gd2-xpm-dev_0.11.5-7+b1_i386 +libgdcm2-dev_2.2.0-14.1_i386 +libgdf-dev_0.1.2-2_i386 +libgdict-1.0-dev_3.4.0-2_i386 +libgdk-pixbuf2.0-dev_2.26.1-1_i386 +libgdkcutter-pixbuf-dev_1.1.7-1.2_i386 +libgdl-3-dev_3.4.2-1_i386 +libgdome2-cpp-smart-dev_0.2.6-6+b1_i386 +libgdome2-dev_0.8.1+debian-4.1_i386 +libgdome2-ocaml-dev_0.2.6-6+b1_i386 +libgdu-dev_3.0.2-3_i386 +libgdu-gtk-dev_3.0.2-3_i386 +libgeant321-2-dev_1:3.21.14.dfsg-10_i386 +libgearman-dev_0.33-2_i386 +libgecode-dev_3.7.3-1_i386 +libgeda-dev_1:1.6.2-4.3_i386 +libgee-dev_0.6.4-2_i386 +libgegl-dev_0.2.0-2+nmu1_i386 +libgeier-dev_0.13-1+b1_i386 +libgenders0-dev_1.18-1_i386 +libgenome-1.3-0-dev_1.3.1-3_i386 +libgensec-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libgeoclue-dev_0.12.0-4_i386 +libgeocode-glib-dev_0.99.0-1_i386 +libgeographiclib-dev_1.21-1_i386 +libgeoip-dev_1.4.8+dfsg-3_i386 +libgeomview-dev_1.9.4-3_i386 +libgeos++-dev_3.3.3-1.1_i386 +libgeos-dev_3.3.3-1.1_i386 +libgeotiff-dev_1.3.0+dfsg-3_i386 +libgeotranz3-dev_3.1-2.1_i386 +libges-0.10-dev_0.10.1-2_i386 +libgetdata-dev_0.7.3-6_i386 +libgetfem++-dev_4.1.1+dfsg1-11_i386 +libgetopt++-dev_0.0.2-p22-3_i386 +libgetopt-ocaml-dev_0.0.20040811-10+b3_i386 +libgettext-ocaml-dev_0.3.4-1+b2_i386 +libgexiv2-dev_0.4.1-3_i386 +libgfarm-dev_2.4.1-1.1_i386 +libgflags-dev_2.0-1_i386 +libgfshare-dev_1.0.5-2_i386 +libghc-acid-state-dev_0.6.3-1+b2_i386 +libghc-active-dev_0.1.0.1-2+b2_i386 +libghc-adjunctions-dev_2.4.0.2-1_i386 +libghc-aeson-dev_0.6.0.2-1+b4_i386 +libghc-agda-dev_2.3.0.1-2_i386 +libghc-algebra-dev_2.1.1.2-1_i386 +libghc-alut-dev_2.1.0.2-4+b1_i386 +libghc-ami-dev_0.1-1+b5_i386 +libghc-ansi-terminal-dev_0.5.5-3+b1_i386 +libghc-ansi-wl-pprint-dev_0.6.4-1+b1_i386 +libghc-arrows-dev_0.4.4.0-3+b1_i386 +libghc-asn1-data-dev_0.6.1.3-2+b3_i386 +libghc-attempt-dev_0.4.0-1+b2_i386 +libghc-attoparsec-conduit-dev_0.4.0.1-1_i386 +libghc-attoparsec-dev_0.10.1.1-2+b1_i386 +libghc-attoparsec-enumerator-dev_0.3-3+b3_i386 +libghc-augeas-dev_0.6.1-1_i386 +libghc-authenticate-dev_1.2.1.1-2+b1_i386 +libghc-base-unicode-symbols-dev_0.2.2.3-1+b1_i386 +libghc-base16-bytestring-dev_0.1.1.4-2+b1_i386 +libghc-base64-bytestring-dev_0.1.1.1-2_i386 +libghc-bifunctors-dev_0.1.3.3-1+b1_i386 +libghc-binary-shared-dev_0.8.1-1+b1_i386 +libghc-bindings-dsl-dev_1.0.15-1+b1_i386 +libghc-bindings-gpgme-dev_0.1.4-1_i386 +libghc-bindings-libzip-dev_0.10-2_i386 +libghc-bitarray-dev_0.0.1-2+b1_i386 +libghc-blaze-builder-conduit-dev_0.4.0.2-1_i386 +libghc-blaze-builder-dev_0.3.1.0-1+b2_i386 +libghc-blaze-builder-enumerator-dev_0.2.0.4-1+b1_i386 +libghc-blaze-html-dev_0.4.3.1-3+b2_i386 +libghc-blaze-markup-dev_0.5.1.0-1_i386 +libghc-blaze-textual-dev_0.2.0.6-2+b2_i386 +libghc-bloomfilter-dev_1.2.6.8-1_i386 +libghc-boolean-dev_0.0.1-2+b1_i386 +libghc-boomerang-dev_1.3.1-1_i386 +libghc-brainfuck-dev_0.1-2+b2_i386 +libghc-byteorder-dev_1.0.3-2+b1_i386 +libghc-bytestring-lexing-dev_0.4.0-1+b1_i386 +libghc-bytestring-mmap-dev_0.2.2-2+b1_i386 +libghc-bytestring-nums-dev_0.3.5-2+b1_i386 +libghc-bytestring-show-dev_0.3.5.1-1+b1_i386 +libghc-bzlib-dev_0.5.0.3-2+b1_i386 +libghc-cabal-file-th-dev_0.2.2-1_i386 +libghc-cairo-dev_0.12.3-1+b1_i386 +libghc-case-insensitive-dev_0.4.0.1-2+b2_i386 +libghc-categories-dev_1.0.3-1+b1_i386 +libghc-cautious-file-dev_1.0.1-1_i386 +libghc-cereal-conduit-dev_0.5-1+b1_i386 +libghc-cereal-dev_0.3.5.2-1_i386 +libghc-certificate-dev_1.2.3-2_i386 +libghc-cgi-dev_3001.1.8.2-2+b3_i386 +libghc-chart-dev_0.15-1+b2_i386 +libghc-chell-dev_0.3-1_i386 +libghc-citeproc-hs-dev_0.3.4-1+b4_i386 +libghc-clientsession-dev_0.7.5-3+b1_i386 +libghc-clock-dev_0.2.0.0-2+b1_i386 +libghc-cmdargs-dev_0.9.5-1+b1_i386 +libghc-colour-dev_2.3.3-1+b1_i386 +libghc-comonad-dev_1.1.1.5-1+b1_i386 +libghc-comonad-transformers-dev_2.1.1.1-1+b1_i386 +libghc-comonads-fd-dev_2.1.1.2-1+b1_i386 +libghc-conduit-dev_0.4.2-2_i386 +libghc-configfile-dev_1.0.6-4+b3_i386 +libghc-configurator-dev_0.2.0.0-1+b2_i386 +libghc-contravariant-dev_0.2.0.2-1+b1_i386 +libghc-convertible-dev_1.0.11.0-3+b3_i386 +libghc-cookie-dev_0.4.0-1+b3_i386 +libghc-cpphs-dev_1.13.3-2+b1_i386 +libghc-cprng-aes-dev_0.2.3-3+b4_i386 +libghc-cpu-dev_0.1.1-1_i386 +libghc-criterion-dev_0.6.0.1-3+b4_i386 +libghc-crypto-api-dev_0.10.2-1+b2_i386 +libghc-crypto-conduit-dev_0.3.2-1+b1_i386 +libghc-crypto-dev_4.2.4-1+b1_i386 +libghc-crypto-pubkey-types-dev_0.1.1-1+b3_i386 +libghc-cryptocipher-dev_0.3.5-1+b1_i386 +libghc-cryptohash-dev_0.7.5-1+b2_i386 +libghc-css-text-dev_0.1.1-3+b2_i386 +libghc-csv-conduit-dev_0.2-1_i386 +libghc-csv-dev_0.1.2-2+b3_i386 +libghc-curl-dev_1.3.7-1+b1_i386 +libghc-darcs-dev_2.8.1-1+b1_i386 +libghc-data-accessor-dev_0.2.2.2-1+b1_i386 +libghc-data-accessor-mtl-dev_0.2.0.3-1+b1_i386 +libghc-data-accessor-template-dev_0.2.1.9-1+b2_i386 +libghc-data-binary-ieee754-dev_0.4.2.1-3+b1_i386 +libghc-data-default-dev_0.4.0-1_i386 +libghc-data-inttrie-dev_0.0.7-1+b1_i386 +libghc-data-lens-dev_2.10.0-1+b1_i386 +libghc-data-memocombinators-dev_0.4.3-1+b1_i386 +libghc-dataenc-dev_0.14.0.3-1+b1_i386 +libghc-datetime-dev_0.2.1-3_i386 +libghc-dbus-dev_0.10.3-1_i386 +libghc-debian-dev_3.64-3_i386 +libghc-diagrams-cairo-dev_0.5.0.2-1_i386 +libghc-diagrams-core-dev_0.5.0.1-1+b1_i386 +libghc-diagrams-dev_0.5-2_all +libghc-diagrams-lib-dev_0.5-2_i386 +libghc-diff-dev_0.1.3-1+b1_i386 +libghc-digest-dev_0.0.1.0-1+b1_i386 +libghc-dimensional-dev_0.10.1.2-2+b1_i386 +libghc-directory-tree-dev_0.10.0-2+b1_i386 +libghc-distributive-dev_0.2.2-1+b1_i386 +libghc-dlist-dev_0.5-3+b1_i386 +libghc-download-curl-dev_0.1.3-3+b3_i386 +libghc-dpkg-dev_0.0.3-1_i386 +libghc-dyre-dev_0.8.7-1_i386 +libghc-edison-api-dev_1.2.1-18+b1_i386 +libghc-edison-core-dev_1.2.1.3-9+b1_i386 +libghc-edit-distance-dev_0.2.1-2_i386 +libghc-editline-dev_0.2.1.0-5+b1_i386 +libghc-ekg-dev_0.3.1.0-1+b2_i386 +libghc-email-validate-dev_0.2.8-1+b3_i386 +libghc-entropy-dev_0.2.1-2+b1_i386 +libghc-enumerator-dev_0.4.19-1+b1_i386 +libghc-erf-dev_2.0.0.0-2+b1_i386 +libghc-event-list-dev_0.1.0.1-1+b1_i386 +libghc-exception-transformers-dev_0.3.0.2-1+b1_i386 +libghc-executable-path-dev_0.0.3-1+b1_i386 +libghc-explicit-exception-dev_0.1.7-1+b1_i386 +libghc-failure-dev_0.2.0.1-1+b1_i386 +libghc-fast-logger-dev_0.0.2-1+b2_i386 +libghc-fastcgi-dev_3001.0.2.3-3+b3_i386 +libghc-fclabels-dev_1.1.3-1+b1_i386 +libghc-feed-dev_0.3.8-3_i386 +libghc-fgl-dev_5.4.2.4-2+b2_i386 +libghc-file-embed-dev_0.0.4.4-1_i386 +libghc-filemanip-dev_0.3.5.2-2+b2_i386 +libghc-filestore-dev_0.5-1_i386 +libghc-filesystem-conduit-dev_0.4.0-1_i386 +libghc-free-dev_2.1.1.1-1+b1_i386 +libghc-ftphs-dev_1.0.8-1+b3_i386 +libghc-gconf-dev_0.12.1-1+b1_i386 +libghc-gd-dev_3000.7.3-1_i386 +libghc-ghc-events-dev_0.4.0.0-2+b1_i386 +libghc-ghc-mtl-dev_1.0.1.1-1+b3_i386 +libghc-ghc-paths-dev_0.1.0.8-2+b1_i386 +libghc-ghc-syb-utils-dev_0.2.1.0-1+b3_i386 +libghc-gio-dev_0.12.3-1+b1_i386 +libghc-github-dev_0.4.0-2_i386 +libghc-gitit-dev_0.10.0.1-1+b1_i386 +libghc-glade-dev_0.12.1-1+b3_i386 +libghc-glfw-dev_0.5.0.1-1+b1_i386 +libghc-glib-dev_0.12.2-1+b1_i386 +libghc-glut-dev_2.1.2.2-1_i386 +libghc-gnuidn-dev_0.2-2+b2_i386 +libghc-gnutls-dev_0.1.2-1+b1_i386 +libghc-gsasl-dev_0.3.4-1+b1_i386 +libghc-gstreamer-dev_0.12.1-1+b2_i386 +libghc-gtk-dev_0.12.3-1+b2_i386 +libghc-gtkglext-dev_0.12.1-1+b3_i386 +libghc-gtksourceview2-dev_0.12.3-1+b3_i386 +libghc-haddock-dev_2.10.0-1+b2_i386 +libghc-hakyll-dev_3.2.7.2-1+b5_i386 +libghc-hamlet-dev_1.0.1.3-1+b1_i386 +libghc-happstack-dev_7.0.0-1+b1_i386 +libghc-happstack-server-dev_7.0.1-1+b1_i386 +libghc-harp-dev_0.4-3+b1_i386 +libghc-hashable-dev_1.1.2.3-1+b2_i386 +libghc-hashed-storage-dev_0.5.9-2+b2_i386 +libghc-hashmap-dev_1.3.0.1-1+b2_i386 +libghc-hashtables-dev_1.0.1.4-1+b1_i386 +libghc-haskeline-dev_0.6.4.7-1+b1_i386 +libghc-haskell-lexer-dev_1.0-3+b1_i386 +libghc-haskell-src-dev_1.0.1.5-1+b2_i386 +libghc-haskelldb-dev_2.1.1-5+b1_i386 +libghc-haskelldb-hdbc-dev_2.1.0-4_i386 +libghc-haskelldb-hdbc-odbc-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-postgresql-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-sqlite3-dev_2.1.0-3_i386 +libghc-haskore-dev_0.2.0.3-2_i386 +libghc-hastache-dev_0.3.3-2+b3_i386 +libghc-haxml-dev_1:1.22.5-2+b2_i386 +libghc-haxr-dev_3000.8.5-1+b3_i386 +libghc-hcard-dev_0.0-2+b2_i386 +libghc-hcwiid-dev_0.0.1-3+b1_i386 +libghc-hdbc-dev_2.3.1.1-1+b3_i386 +libghc-hdbc-odbc-dev_2.2.3.0-5+b3_i386 +libghc-hdbc-postgresql-dev_2.3.2.1-1+b3_i386 +libghc-hdbc-sqlite3-dev_2.3.3.0-1+b3_i386 +libghc-hfuse-dev_0.2.4.1-1_i386 +libghc-highlighting-kate-dev_0.5.1-1_i386 +libghc-hinotify-dev_0.3.2-1+b1_i386 +libghc-hint-dev_0.3.3.4-2+b4_i386 +libghc-hipmunk-dev_5.2.0.8-1+b1_i386 +libghc-hjavascript-dev_0.4.7-3+b1_i386 +libghc-hjscript-dev_0.5.0-3+b2_i386 +libghc-hjsmin-dev_0.1.1-1+b2_i386 +libghc-hlint-dev_1.8.28-1+b3_i386 +libghc-hoauth-dev_0.3.4-1+b1_i386 +libghc-hostname-dev_1.0-4+b1_i386 +libghc-hs-bibutils-dev_4.12-5+b2_i386 +libghc-hs3-dev_0.5.6-2+b4_i386 +libghc-hscolour-dev_1.19-3+b1_i386 +libghc-hscurses-dev_1.4.1.0-1+b2_i386 +libghc-hsemail-dev_1.7.1-2+b3_i386 +libghc-hsh-dev_2.0.3-6+b3_i386 +libghc-hslogger-dev_1.1.4+dfsg1-2+b3_i386 +libghc-hsp-dev_0.6.1-2+b3_i386 +libghc-hspec-dev_1.1.0-1+b1_i386 +libghc-hsql-dev_1.8.1-4_i386 +libghc-hsql-mysql-dev_1.8.1-4+b1_i386 +libghc-hsql-odbc-dev_1.8.1.1-2_i386 +libghc-hsql-postgresql-dev_1.8.1-3_i386 +libghc-hsql-sqlite3-dev_1.8.1-2_i386 +libghc-hssyck-dev_0.50-2+b2_i386 +libghc-hstringtemplate-dev_0.6.8-1_i386 +libghc-hsx-dev_0.9.1-3_i386 +libghc-html-conduit-dev_0.0.1-2_i386 +libghc-html-dev_1.0.1.2-5+b1_i386 +libghc-http-conduit-dev_1.4.1.6-3_i386 +libghc-http-date-dev_0.0.2-1+b2_i386 +libghc-http-dev_1:4000.2.3-1+b2_i386 +libghc-http-types-dev_0.6.11-1_i386 +libghc-hunit-dev_1.2.4.2-2+b1_i386 +libghc-hxt-cache-dev_9.0.2-2+b3_i386 +libghc-hxt-charproperties-dev_9.1.1-2+b1_i386 +libghc-hxt-curl-dev_9.1.1-1+b4_i386 +libghc-hxt-dev_9.2.2-2+b3_i386 +libghc-hxt-http-dev_9.1.4-2+b3_i386 +libghc-hxt-regex-xmlschema-dev_9.0.4-2+b3_i386 +libghc-hxt-relaxng-dev_9.1.4-1+b3_i386 +libghc-hxt-tagsoup-dev_9.1.1-1+b4_i386 +libghc-hxt-unicode-dev_9.0.2-2+b1_i386 +libghc-hxt-xpath-dev_9.1.2-1+b4_i386 +libghc-hxt-xslt-dev_9.1.1-1+b3_i386 +libghc-iconv-dev_0.4.1.0-2+b1_i386 +libghc-ieee754-dev_0.7.3-1+b1_i386 +libghc-ifelse-dev_0.85-4+b1_i386 +libghc-io-choice-dev_0.0.1-1+b3_i386 +libghc-io-storage-dev_0.3-2+b1_i386 +libghc-iospec-dev_0.2.5-1+b2_i386 +libghc-irc-dev_0.5.0.0-1+b3_i386 +libghc-iteratee-dev_0.8.8.2-2+b1_i386 +libghc-ixset-dev_1.0.3-2+b1_i386 +libghc-json-dev_0.5-2+b2_i386 +libghc-keys-dev_2.1.3.2-1+b1_i386 +libghc-knob-dev_0.1.1-1_i386 +libghc-lambdabot-utils-dev_4.2.1-3+b3_i386 +libghc-language-c-dev_0.4.2-2+b2_i386 +libghc-language-haskell-extract-dev_0.2.1-4+b1_i386 +libghc-language-javascript-dev_0.5.4-1+b2_i386 +libghc-largeword-dev_1.0.1-2+b1_i386 +libghc-lazysmallcheck-dev_0.6-1+b1_i386 +libghc-ldap-dev_0.6.6-4.1+b1_i386 +libghc-leksah-server-dev_0.12.0.4-3_i386 +libghc-libtagc-dev_0.12.0-2+b1_i386 +libghc-libxml-sax-dev_0.7.2-2+b1_i386 +libghc-libzip-dev_0.10-1+b2_i386 +libghc-lifted-base-dev_0.1.1-1+b1_i386 +libghc-listlike-dev_3.1.4-1+b1_i386 +libghc-llvm-base-dev_3.0.1.0-1_i386 +libghc-llvm-dev_3.0.1.0-1+b1_i386 +libghc-logict-dev_0.5.0.1-1+b1_i386 +libghc-ltk-dev_0.12.0.0-2+b1_i386 +libghc-maccatcher-dev_2.1.5-2+b3_i386 +libghc-magic-dev_1.0.8-8+b1_i386 +libghc-markov-chain-dev_0.0.3.2-1+b1_i386 +libghc-math-functions-dev_0.1.1.0-2+b2_i386 +libghc-maths-dev_0.4.3-1+b1_i386 +libghc-maybet-dev_0.1.2-3+b2_i386 +libghc-mbox-dev_0.1-2+b1_i386 +libghc-memotrie-dev_0.5-1_i386 +libghc-mersenne-random-dev_1.0.0.1-2+b1_i386 +libghc-midi-dev_0.2.0.1-1+b1_i386 +libghc-mime-mail-dev_0.4.1.1-2+b3_i386 +libghc-missingh-dev_1.1.0.3-6+b3_i386 +libghc-mmap-dev_0.5.7-2+b1_i386 +libghc-monad-control-dev_0.3.1.3-1+b1_i386 +libghc-monad-loops-dev_0.3.2.0-1_i386 +libghc-monad-par-dev_0.1.0.3-2+b1_i386 +libghc-monadcatchio-mtl-dev_0.3.0.4-2+b2_i386 +libghc-monadcatchio-transformers-dev_0.3.0.0-2+b1_i386 +libghc-monadcryptorandom-dev_0.4.1-1+b2_i386 +libghc-monadrandom-dev_0.1.6-2+b2_i386 +libghc-monads-tf-dev_0.1.0.0-1+b2_i386 +libghc-monoid-transformer-dev_0.0.2-3+b1_i386 +libghc-mtl-dev_2.1.1-1_i386 +libghc-mtlparse-dev_0.1.2-2+b2_i386 +libghc-murmur-hash-dev_0.1.0.5-2+b1_i386 +libghc-mwc-random-dev_0.11.0.0-4+b1_i386 +libghc-ncurses-dev_0.2.1-1+b1_i386 +libghc-netwire-dev_3.1.0-2+b5_i386 +libghc-network-conduit-dev_0.4.0.1-2_i386 +libghc-network-dev_2.3.0.13-1+b2_i386 +libghc-network-protocol-xmpp-dev_0.4.3-1_i386 +libghc-newtype-dev_0.2-1_i386 +libghc-non-negative-dev_0.1-2+b1_i386 +libghc-numbers-dev_2009.8.9-2+b1_i386 +libghc-numeric-quest-dev_0.2-1+b1_i386 +libghc-numinstances-dev_1.0-2+b1_i386 +libghc-numtype-dev_1.0-2+b1_i386 +libghc-oeis-dev_0.3.1-2+b3_i386 +libghc-openal-dev_1.3.1.3-4+b1_i386 +libghc-opengl-dev_2.2.3.1-1+b1_i386 +libghc-openpgp-asciiarmor-dev_0.1-1+b2_i386 +libghc-options-dev_0.1.1-1_i386 +libghc-pandoc-dev_1.9.4.2-2_i386 +libghc-pandoc-types-dev_1.9.1-1+b2_i386 +libghc-pango-dev_0.12.2-1+b2_i386 +libghc-parallel-dev_3.2.0.2-2+b1_i386 +libghc-parseargs-dev_0.1.3.2-2+b1_i386 +libghc-parsec2-dev_2.1.0.1-6+b1_i386 +libghc-parsec3-dev_3.1.2-1+b3_i386 +libghc-pastis-dev_0.1.2-2+b3_i386 +libghc-path-pieces-dev_0.1.0-1+b2_i386 +libghc-patience-dev_0.1.1-1_i386 +libghc-pcre-light-dev_0.4-3+b1_i386 +libghc-pem-dev_0.1.1-1+b3_i386 +libghc-persistent-dev_0.9.0.4-2_i386 +libghc-persistent-sqlite-dev_0.9.0.2-2_i386 +libghc-persistent-template-dev_0.9.0.2-1_i386 +libghc-polyparse-dev_1.7-1+b2_i386 +libghc-pool-conduit-dev_0.1.0.2-1_i386 +libghc-postgresql-libpq-dev_0.8.2-1_i386 +libghc-postgresql-simple-dev_0.1.4.3-1_i386 +libghc-pretty-show-dev_1.1.1-4+b1_i386 +libghc-primes-dev_0.2.1.0-2+b1_i386 +libghc-primitive-dev_0.4.1-1+b1_i386 +libghc-psqueue-dev_1.1-2+b1_i386 +libghc-puremd5-dev_2.1.0.3-2+b4_i386 +libghc-pwstore-fast-dev_2.2-2+b4_i386 +libghc-quickcheck1-dev_1.2.0.1-2+b1_i386 +libghc-quickcheck2-dev_2.4.2-1+b1_i386 +libghc-random-dev_1.0.1.1-1+b1_i386 +libghc-random-shuffle-dev_0.0.3-2+b2_i386 +libghc-ranged-sets-dev_0.3.0-2+b1_i386 +libghc-ranges-dev_0.2.4-2+b1_i386 +libghc-reactive-banana-dev_0.6.0.0-1+b3_i386 +libghc-readline-dev_1.0.1.0-3+b1_i386 +libghc-recaptcha-dev_0.1-4+b3_i386 +libghc-regex-base-dev_0.93.2-2+b2_i386 +libghc-regex-compat-dev_0.95.1-2+b1_i386 +libghc-regex-pcre-dev_0.94.2-2+b1_i386 +libghc-regex-posix-dev_0.95.1-2+b1_i386 +libghc-regex-tdfa-dev_1.1.8-2+b1_i386 +libghc-regex-tdfa-utf8-dev_1.0-5+b3_i386 +libghc-regexpr-dev_0.5.4-2+b2_i386 +libghc-representable-functors-dev_2.4.0.2-1+b1_i386 +libghc-representable-tries-dev_2.4.0.2-1_i386 +libghc-resource-pool-dev_0.2.1.0-2+b4_i386 +libghc-resourcet-dev_0.3.2.1-1+b1_i386 +libghc-rsa-dev_1.2.1.0-1+b1_i386 +libghc-safe-dev_0.3.3-1+b1_i386 +libghc-safecopy-dev_0.6.1-1+b1_i386 +libghc-sdl-dev_0.6.3-1+b1_i386 +libghc-sdl-gfx-dev_0.6.0-3+b1_i386 +libghc-sdl-image-dev_0.6.1-3+b1_i386 +libghc-sdl-mixer-dev_0.6.1-3+b1_i386 +libghc-sdl-ttf-dev_0.6.1-3+b1_i386 +libghc-semigroupoids-dev_1.3.1.2-1+b1_i386 +libghc-semigroups-dev_0.8.3.2-1_i386 +libghc-sendfile-dev_0.7.6-1+b2_i386 +libghc-sha-dev_1.5.0.1-1_i386 +libghc-shakespeare-css-dev_1.0.1.2-1+b1_i386 +libghc-shakespeare-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-i18n-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-js-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-text-dev_1.0.0.2-1+b1_i386 +libghc-shellac-dev_0.9.5.1-2+b2_i386 +libghc-show-dev_0.4.1.2-1+b2_i386 +libghc-silently-dev_1.1.4-1+b2_i386 +libghc-simple-sendfile-dev_0.2.3-1+b2_i386 +libghc-simpleea-dev_0.1.1-2+b2_i386 +libghc-simpleirc-dev_0.2.1-2+b3_i386 +libghc-skein-dev_0.1.0.7-2+b1_i386 +libghc-smallcheck-dev_0.6-1+b1_i386 +libghc-smtpclient-dev_1.0.4-3+b3_i386 +libghc-snap-core-dev_0.8.1-1+b4_i386 +libghc-snap-server-dev_0.8.1.1-1_i386 +libghc-socks-dev_0.4.1-1+b4_i386 +libghc-split-dev_0.1.4.2-2_i386 +libghc-src-exts-dev_1.11.1-3+b1_i386 +libghc-statevar-dev_1.0.0.0-2+b1_i386 +libghc-static-hash-dev_0.0.1-3+b2_i386 +libghc-statistics-dev_0.10.1.0-2+b1_i386 +libghc-stm-dev_2.3-1_i386 +libghc-stream-dev_0.4.6-1+b1_i386 +libghc-strict-concurrency-dev_0.2.4.1-2+b1_i386 +libghc-strict-dev_0.3.2-2+b1_i386 +libghc-strptime-dev_1.0.6-1_i386 +libghc-svgcairo-dev_0.12.1-1+b2_i386 +libghc-syb-dev_0.3.6.1-1_i386 +libghc-syb-with-class-dev_0.6.1.3-1+b1_i386 +libghc-syb-with-class-instances-text-dev_0.0.1-3+b2_i386 +libghc-system-fileio-dev_0.3.8-1_i386 +libghc-system-filepath-dev_0.4.6-1+b2_i386 +libghc-tagged-dev_0.4.2.1-1_i386 +libghc-tagsoup-dev_0.12.6-1+b3_i386 +libghc-tagstream-conduit-dev_0.3.2-1_i386 +libghc-tar-dev_0.3.2.0-2+b1_i386 +libghc-template-dev_0.2.0.7-1+b1_i386 +libghc-temporary-dev_1.1.2.3-1+b1_i386 +libghc-terminfo-dev_0.3.2.3-1+b1_i386 +libghc-test-framework-dev_0.6-1+b1_i386 +libghc-test-framework-hunit-dev_0.2.7-1+b3_i386 +libghc-test-framework-quickcheck2-dev_0.2.12.1-1+b1_i386 +libghc-test-framework-th-dev_0.2.2-5_i386 +libghc-test-framework-th-prime-dev_0.0.5-1_i386 +libghc-testpack-dev_2.1.1-1+b2_i386 +libghc-texmath-dev_0.6.0.6-1+b2_i386 +libghc-text-dev_0.11.2.0-1_i386 +libghc-text-icu-dev_0.6.3.4-2+b2_i386 +libghc-tinyurl-dev_0.1.0-2+b3_i386 +libghc-tls-dev_0.9.5-1+b4_i386 +libghc-tls-extra-dev_0.4.6.1-2_i386 +libghc-tokyocabinet-dev_0.0.5-5+b3_i386 +libghc-transformers-base-dev_0.4.1-2+b2_i386 +libghc-transformers-dev_0.3.0.0-1_i386 +libghc-type-level-dev_0.2.4-5_i386 +libghc-uniplate-dev_1.6.7-1+b2_i386 +libghc-unix-bytestring-dev_0.3.5-2+b1_i386 +libghc-unix-compat-dev_0.3.0.1-1+b1_i386 +libghc-unixutils-dev_1.50-1+b1_i386 +libghc-unlambda-dev_0.1-2+b2_i386 +libghc-unordered-containers-dev_0.2.1.0-1_i386 +libghc-uri-dev_0.1.6-1+b2_i386 +libghc-url-dev_2.1.2-4+b1_i386 +libghc-utf8-light-dev_0.4.0.1-2+b1_i386 +libghc-utf8-string-dev_0.3.7-1+b1_i386 +libghc-utility-ht-dev_0.0.5.1-3+b1_i386 +libghc-uuagc-cabal-dev_1.0.2.0-1+b1_i386 +libghc-uuid-dev_1.2.3-2+b4_i386 +libghc-uulib-dev_0.9.14-2_i386 +libghc-vault-dev_0.2.0.0-1+b2_i386 +libghc-vector-algorithms-dev_0.5.4-1+b2_i386 +libghc-vector-dev_0.9.1-2+b1_i386 +libghc-vector-space-dev_0.8.1-1_i386 +libghc-vector-space-points-dev_0.1.1.0-1+b1_i386 +libghc-void-dev_0.5.5.1-2+b1_i386 +libghc-vte-dev_0.12.1-1+b3_i386 +libghc-vty-dev_4.7.0.14-1+b1_i386 +libghc-wai-app-file-cgi-dev_0.5.8-1+b4_i386 +libghc-wai-app-static-dev_1.2.0.3-1+b3_i386 +libghc-wai-dev_1.2.0.2-1+b2_i386 +libghc-wai-extra-dev_1.2.0.4-1_i386 +libghc-wai-logger-dev_0.1.4-1+b6_i386 +libghc-wai-logger-prefork-dev_0.1.3-1+b6_i386 +libghc-wai-test-dev_1.2.0.2-1_i386 +libghc-warp-dev_1.2.1.1-1_i386 +libghc-warp-tls-dev_1.2.0.4-1+b4_i386 +libghc-web-routes-dev_0.25.3-2+b3_i386 +libghc-webkit-dev_0.12.3-2+b1_i386 +libghc-weighted-regexp-dev_0.3.1.1-2+b1_i386 +libghc-x11-dev_1.5.0.1-1+b2_i386 +libghc-x11-xft-dev_0.3.1-1+b3_i386 +libghc-xdg-basedir-dev_0.2.1-2+b1_i386 +libghc-xhtml-dev_3000.2.1-1_i386 +libghc-xml-conduit-dev_0.7.0.2-1_i386 +libghc-xml-dev_1.3.12-1+b2_i386 +libghc-xml-types-dev_0.3.1-2+b2_i386 +libghc-xml2html-dev_0.1.2.3-1_i386 +libghc-xmonad-contrib-dev_0.10-4~deb7u1_i386 +libghc-xmonad-dev_0.10-4+b2_i386 +libghc-xss-sanitize-dev_0.3.2-1+b1_i386 +libghc-yaml-dev_0.7.0.2-1+b2_i386 +libghc-yaml-light-dev_0.1.4-2+b2_i386 +libghc-yesod-auth-dev_1.0.2.1-2+b2_i386 +libghc-yesod-core-dev_1.0.1.2-1+b3_i386 +libghc-yesod-default-dev_1.0.1.1-1+b1_i386 +libghc-yesod-dev_1.0.1.6-2+b3_i386 +libghc-yesod-form-dev_1.0.0.4-1+b1_i386 +libghc-yesod-json-dev_1.0.0.1-1+b3_i386 +libghc-yesod-markdown-dev_0.4.0-1+b3_i386 +libghc-yesod-persistent-dev_1.0.0.1-1+b1_i386 +libghc-yesod-routes-dev_1.0.1.2-1_i386 +libghc-yesod-static-dev_1.0.0.2-1+b3_i386 +libghc-yesod-test-dev_0.2.0.6-1_i386 +libghc-zip-archive-dev_0.1.1.7-3+b2_i386 +libghc-zlib-bindings-dev_0.1.0.1-1_i386 +libghc-zlib-conduit-dev_0.4.0.1-1_i386 +libghc-zlib-dev_0.5.3.3-1+b1_i386 +libghc-zlib-enum-dev_0.2.2.1-1+b1_i386 +libghc6-agda-dev_1:8_all +libghc6-alut-dev_1:8_all +libghc6-arrows-dev_1:8_all +libghc6-binary-dev_1:8_all +libghc6-binary-shared-dev_1:8_all +libghc6-bzlib-dev_1:8_all +libghc6-cairo-dev_1:8_all +libghc6-cautious-file-dev_1:8_all +libghc6-cgi-dev_1:8_all +libghc6-colour-dev_1:8_all +libghc6-configfile-dev_1:8_all +libghc6-convertible-dev_1:8_all +libghc6-cpphs-dev_1:8_all +libghc6-criterion-dev_1:8_all +libghc6-csv-dev_1:8_all +libghc6-curl-dev_1:8_all +libghc6-data-accessor-dev_1:8_all +libghc6-dataenc-dev_1:8_all +libghc6-datetime-dev_1:8_all +libghc6-debian-dev_1:8_all +libghc6-deepseq-dev_1:8_all +libghc6-diagrams-dev_1:8_all +libghc6-diff-dev_1:8_all +libghc6-digest-dev_1:8_all +libghc6-edison-api-dev_1:8_all +libghc6-edison-core-dev_1:8_all +libghc6-editline-dev_1:8_all +libghc6-erf-dev_1:8_all +libghc6-event-list-dev_1:8_all +libghc6-explicit-exception-dev_1:8_all +libghc6-fastcgi-dev_1:8_all +libghc6-feed-dev_1:8_all +libghc6-fgl-dev_1:8_all +libghc6-filemanip-dev_1:8_all +libghc6-filestore-dev_1:8_all +libghc6-ftphs-dev_1:8_all +libghc6-gconf-dev_1:8_all +libghc6-ghc-events-dev_1:8_all +libghc6-ghc-mtl-dev_1:8_all +libghc6-ghc-paths-dev_1:8_all +libghc6-gio-dev_1:8_all +libghc6-gitit-dev_1:8_all +libghc6-glade-dev_1:8_all +libghc6-glfw-dev_1:8_all +libghc6-glib-dev_1:8_all +libghc6-glut-dev_1:8_all +libghc6-gstreamer-dev_1:8_all +libghc6-gtk-dev_1:8_all +libghc6-gtkglext-dev_1:8_all +libghc6-gtksourceview2-dev_1:8_all +libghc6-haddock-dev_1:8_all +libghc6-happstack-dev_1:8_all +libghc6-happstack-server-dev_1:8_all +libghc6-harp-dev_1:8_all +libghc6-hashed-storage-dev_1:8_all +libghc6-haskeline-dev_1:8_all +libghc6-haskell-lexer-dev_1:8_all +libghc6-haskell-src-dev_1:8_all +libghc6-haskelldb-dev_1:8_all +libghc6-haskelldb-hdbc-dev_1:8_all +libghc6-haskelldb-hdbc-odbc-dev_1:8_all +libghc6-haskelldb-hdbc-postgresql-dev_1:8_all +libghc6-haskelldb-hdbc-sqlite3-dev_1:8_all +libghc6-haskore-dev_1:8_all +libghc6-haxml-dev_1:8_all +libghc6-haxr-dev_1:8_all +libghc6-hdbc-dev_1:8_all +libghc6-hdbc-odbc-dev_1:8_all +libghc6-hdbc-postgresql-dev_1:8_all +libghc6-hdbc-sqlite3-dev_1:8_all +libghc6-highlighting-kate-dev_1:8_all +libghc6-hint-dev_1:8_all +libghc6-hjavascript-dev_1:8_all +libghc6-hjscript-dev_1:8_all +libghc6-hoauth-dev_1:8_all +libghc6-hscolour-dev_1:8_all +libghc6-hscurses-dev_1:8_all +libghc6-hsemail-dev_1:8_all +libghc6-hsh-dev_1:8_all +libghc6-hslogger-dev_1:8_all +libghc6-hsp-dev_1:8_all +libghc6-hsql-dev_1:8_all +libghc6-hsql-mysql-dev_1:8_all +libghc6-hsql-odbc-dev_1:8_all +libghc6-hsql-postgresql-dev_1:8_all +libghc6-hsql-sqlite3-dev_1:8_all +libghc6-hstringtemplate-dev_1:8_all +libghc6-hsx-dev_1:8_all +libghc6-html-dev_1:8_all +libghc6-http-dev_1:8_all +libghc6-hunit-dev_1:8_all +libghc6-hxt-dev_1:8_all +libghc6-ifelse-dev_1:8_all +libghc6-irc-dev_1:8_all +libghc6-json-dev_1:8_all +libghc6-language-c-dev_1:8_all +libghc6-lazysmallcheck-dev_1:8_all +libghc6-ldap-dev_1:8_all +libghc6-leksah-server-dev_1:8_all +libghc6-llvm-dev_1:8_all +libghc6-ltk-dev_1:8_all +libghc6-magic-dev_1:8_all +libghc6-markov-chain-dev_1:8_all +libghc6-maybet-dev_1:8_all +libghc6-midi-dev_1:8_all +libghc6-missingh-dev_1:8_all +libghc6-mmap-dev_1:8_all +libghc6-monadcatchio-mtl-dev_1:8_all +libghc6-monoid-transformer-dev_1:8_all +libghc6-mtl-dev_1:8_all +libghc6-mwc-random-dev_1:8_all +libghc6-network-dev_1:8_all +libghc6-non-negative-dev_1:8_all +libghc6-openal-dev_1:8_all +libghc6-opengl-dev_1:8_all +libghc6-pandoc-dev_1:8_all +libghc6-pango-dev_1:8_all +libghc6-parallel-dev_1:8_all +libghc6-parsec2-dev_1:8_all +libghc6-parsec3-dev_1:8_all +libghc6-pcre-light-dev_1:8_all +libghc6-polyparse-dev_1:8_all +libghc6-pretty-show-dev_1:8_all +libghc6-primitive-dev_1:8_all +libghc6-quickcheck1-dev_1:8_all +libghc6-quickcheck2-dev_1:8_all +libghc6-recaptcha-dev_1:8_all +libghc6-regex-base-dev_1:8_all +libghc6-regex-compat-dev_1:8_all +libghc6-regex-posix-dev_1:8_all +libghc6-regex-tdfa-dev_1:8_all +libghc6-regex-tdfa-utf8-dev_1:8_all +libghc6-safe-dev_1:8_all +libghc6-sdl-dev_1:8_all +libghc6-sdl-gfx-dev_1:8_all +libghc6-sdl-image-dev_1:8_all +libghc6-sdl-mixer-dev_1:8_all +libghc6-sdl-ttf-dev_1:8_all +libghc6-sendfile-dev_1:8_all +libghc6-sha-dev_1:8_all +libghc6-smtpclient-dev_1:8_all +libghc6-split-dev_1:8_all +libghc6-src-exts-dev_1:8_all +libghc6-statistics-dev_1:8_all +libghc6-stm-dev_1:8_all +libghc6-stream-dev_1:8_all +libghc6-strict-concurrency-dev_1:8_all +libghc6-svgcairo-dev_1:8_all +libghc6-syb-with-class-dev_1:8_all +libghc6-syb-with-class-instances-text-dev_1:8_all +libghc6-tagsoup-dev_1:8_all +libghc6-tar-dev_1:8_all +libghc6-terminfo-dev_1:8_all +libghc6-testpack-dev_1:8_all +libghc6-texmath-dev_1:8_all +libghc6-text-dev_1:8_all +libghc6-tokyocabinet-dev_1:8_all +libghc6-transformers-dev_1:8_all +libghc6-type-level-dev_1:8_all +libghc6-uniplate-dev_1:8_all +libghc6-unix-compat-dev_1:8_all +libghc6-unixutils-dev_1:8_all +libghc6-url-dev_1:8_all +libghc6-utility-ht-dev_1:8_all +libghc6-uulib-dev_1:8_all +libghc6-vector-algorithms-dev_1:8_all +libghc6-vector-dev_1:8_all +libghc6-vte-dev_1:8_all +libghc6-vty-dev_1:8_all +libghc6-webkit-dev_1:8_all +libghc6-x11-dev_1:8_all +libghc6-x11-xft-dev_1:8_all +libghc6-xhtml-dev_1:8_all +libghc6-xml-dev_1:8_all +libghc6-xmonad-contrib-dev_1:8_all +libghc6-xmonad-dev_1:8_all +libghc6-zip-archive-dev_1:8_all +libghc6-zlib-dev_1:8_all +libghemical-dev_3.0.0-2_i386 +libgif-dev_4.1.6-10_i386 +libgiftiio-dev_1.0.9-1_i386 +libgig-dev_3.3.0-2_i386 +libgii1-dev_1:1.0.2-4.1_i386 +libgimp2.0-dev_2.8.2-2+deb7u1_i386 +libginac-dev_1.6.2-1_i386 +libginspx-dev_20050529-3.1_i386 +libgio2.0-cil-dev_2.22.3-2_all +libgirara-dev_0.1.2-3_i386 +libgirepository1.0-dev_1.32.1-1_i386 +libgjs-dev_1.32.0-5_i386 +libgkeyfile-cil-dev_0.1-4_all +libgksu2-dev_2.0.13~pre1-6_i386 +libgl1-mesa-dev_8.0.5-4+deb7u2_i386 +libgl1-mesa-swx11-dev_8.0.5-4+deb7u2_i386 +libgl2ps-dev_1.3.6-1_i386 +libglade2-dev_1:2.6.4-1_i386 +libglade2.0-cil-dev_2.12.10-5_i386 +libglademm-2.4-dev_2.6.7-2_i386 +libgladeui-1-dev_3.6.7-2.1_i386 +libgladeui-dev_3.12.1-1_i386 +libglbsp-dev_2.24-1_i386 +libglc-dev_0.7.2-5+b1_i386 +libgle3-dev_3.1.0-7_i386 +libgles1-mesa-dev_8.0.5-4+deb7u2_i386 +libgles2-mesa-dev_8.0.5-4+deb7u2_i386 +libglew-dev_1.7.0-3_i386 +libglewmx-dev_1.7.0-3_i386 +libglfw-dev_2.7.2-1_i386 +libglib2.0-cil-dev_2.12.10-5_i386 +libglib2.0-dev_2.33.12+really2.32.4-5_i386 +libglibmm-2.4-dev_2.32.1-1_i386 +libglide2-dev_2002.04.10ds1-7_i386 +libglide3-dev_2002.04.10ds1-7_i386 +libglm-dev_0.9.3.3+dfsg-0.1_all +libglobus-authz-callout-error-dev_2.2-1_i386 +libglobus-authz-dev_2.2-1_i386 +libglobus-callout-dev_2.2-1_i386 +libglobus-common-dev_14.7-2_i386 +libglobus-ftp-client-dev_7.3-1_i386 +libglobus-ftp-control-dev_4.4-1_i386 +libglobus-gass-cache-dev_8.1-2_i386 +libglobus-gass-copy-dev_8.4-1_i386 +libglobus-gass-server-ez-dev_4.3-1_i386 +libglobus-gass-transfer-dev_7.2-1_i386 +libglobus-gfork-dev_3.2-1_i386 +libglobus-gram-client-dev_12.4-1_i386 +libglobus-gram-job-manager-callout-error-dev_2.1-2_i386 +libglobus-gram-protocol-dev_11.3-1_i386 +libglobus-gridftp-server-control-dev_2.5-2_i386 +libglobus-gridftp-server-dev_6.10-2_i386 +libglobus-gridmap-callout-error-dev_1.2-2_i386 +libglobus-gsi-callback-dev_4.2-1_i386 +libglobus-gsi-cert-utils-dev_8.3-1_i386 +libglobus-gsi-credential-dev_5.3-1_i386 +libglobus-gsi-openssl-error-dev_2.1-2_i386 +libglobus-gsi-proxy-core-dev_6.2-1_i386 +libglobus-gsi-proxy-ssl-dev_4.1-2_i386 +libglobus-gsi-sysconfig-dev_5.2-1_i386 +libglobus-gss-assist-dev_8.5-1_i386 +libglobus-gssapi-error-dev_4.1-2_i386 +libglobus-gssapi-gsi-dev_10.6-1_i386 +libglobus-io-dev_9.3-1_i386 +libglobus-openssl-module-dev_3.2-1_i386 +libglobus-rls-client-dev_5.2-8_i386 +libglobus-rsl-dev_9.1-2_i386 +libglobus-scheduler-event-generator-dev_4.6-1_i386 +libglobus-usage-dev_3.1-2_i386 +libglobus-xio-dev_3.3-1_i386 +libglobus-xio-gsi-driver-dev_2.3-1_i386 +libglobus-xio-pipe-driver-dev_2.2-1_i386 +libglobus-xio-popen-driver-dev_2.3-1_i386 +libgloox-dev_1.0-1.1_i386 +libglpk-dev_4.45-1_i386 +libglrr-glib-dev_20050529-3.1_i386 +libglrr-gobject-dev_20050529-3.1_i386 +libglrr-gtk-dev_20050529-3.1_i386 +libglrr-widgets-dev_20050529-3.1_i386 +libglu1-mesa-dev_8.0.5-4+deb7u2_i386 +libglui-dev_2.36-4_i386 +libglw1-mesa-dev_8.0.0-1_i386 +libgme-dev_0.5.5-2_i386 +libgmerlin-avdec-dev_1.2.0~dfsg-1+b1_i386 +libgmerlin-dev_1.2.0~dfsg+1-1_i386 +libgmime-2.6-dev_2.6.10-1_i386 +libgmime2.6-cil-dev_2.6.10-1_all +libgmlib-dev_1.0.6-1_i386 +libgmm++-dev_4.1.1+dfsg1-11_all +libgmp-dev_2:5.0.5+dfsg-2_i386 +libgmp-ocaml-dev_20021123-17+b3_i386 +libgmp3-dev_2:5.0.5+dfsg-2_i386 +libgmpada3-dev_0.0.20120331-1_i386 +libgmt-dev_4.5.7-2_i386 +libgmtk-dev_1.0.6-1_i386 +libgnadecommon2-dev_1.6.2-9_i386 +libgnadeodbc2-dev_1.6.2-9_i386 +libgnadesqlite3-2-dev_1.6.2-9_i386 +libgnatprj4.6-dev_4.6.3-8_i386 +libgnatvsn4.6-dev_4.6.3-8_i386 +libgnelib-dev_0.75+svn20091130-1+b1_i386 +libgnet-dev_2.0.8-2.2_i386 +libgnokii-dev_0.6.30+dfsg-1+b1_i386 +libgnome-bluetooth-dev_3.4.2-1_i386 +libgnome-desktop-3-dev_3.4.2-1_i386 +libgnome-desktop-dev_2.32.1-2_i386 +libgnome-keyring-dev_3.4.1-1_i386 +libgnome-keyring1.0-cil-dev_1.0.0-4_i386 +libgnome-mag-dev_1:0.16.3-1_i386 +libgnome-media-profiles-dev_3.0.0-1_i386 +libgnome-menu-3-dev_3.4.2-5_i386 +libgnome-menu-dev_3.0.1-4_i386 +libgnome-speech-dev_1:0.4.25-5_i386 +libgnome-vfs2.0-cil-dev_2.24.2-3_all +libgnome-vfsmm-2.6-dev_2.26.0-1_i386 +libgnome2-dev_2.32.1-3_i386 +libgnome2.0-cil-dev_2.24.2-3_i386 +libgnomeada2.24.1-dev_2.24.1-7_i386 +libgnomecanvas2-dev_2.30.3-1.2_i386 +libgnomecanvasmm-2.6-dev_2.26.0-1_i386 +libgnomecups1.0-dev_0.2.3-5_i386 +libgnomedesktop2.0-cil-dev_2.26.0-8_all +libgnomekbd-dev_3.4.0.2-1_i386 +libgnomemm-2.6-dev_2.30.0-1_i386 +libgnomeprint2.2-dev_2.18.8-3_i386 +libgnomeprintui2.2-dev_2.18.6-3_i386 +libgnomeui-dev_2.24.5-2_i386 +libgnomeuimm-2.6-dev_2.28.0-1_i386 +libgnomevfs2-dev_1:2.24.4-2_i386 +libgnuift0-dev_0.1.14-12_i386 +libgnuplot-ocaml-dev_0.8.3-3_i386 +libgnustep-base-dev_1.22.1-4_i386 +libgnustep-dl2-dev_0.12.0-9+nmu1_i386 +libgnustep-gui-dev_0.20.0-3_i386 +libgnutls-dev_2.12.20-8+deb7u1_i386 +libgoa-1.0-dev_3.4.2-2_i386 +libgoffice-0.8-dev_0.8.17-1.2_i386 +libgofigure-dev_0.9.0-1+b2_i386 +libgoocanvas-dev_0.15-1_i386 +libgoocanvasmm-dev_0.15.4-1_i386 +libgoogle-perftools-dev_2.0-2_i386 +libgooglepinyin0-dev_0.1.2-1_i386 +libgpac-dev_0.5.0~dfsg0-1_i386 +libgpds-dev_1.5.1-6_i386 +libgpelaunch-dev_0.14-6_i386 +libgpepimc-dev_0.9-4_i386 +libgpeschedule-dev_0.17-4_i386 +libgpevtype-dev_0.50-6_i386 +libgpewidget-dev_0.117-6_i386 +libgpg-error-dev_1.10-3.1_i386 +libgpgme11-dev_1.2.0-1.4_i386 +libgphoto2-2-dev_2.4.14-2_i386 +libgpiv3-dev_0.6.1-4_i386 +libgpm-dev_1.20.4-6_i386 +libgpod-cil-dev_0.8.2-7_i386 +libgpod-dev_0.8.2-7_i386 +libgpod-nogtk-dev_0.8.2-7_i386 +libgportugol-dev_1.1-2_i386 +libgps-dev_3.6-4+deb7u1_i386 +libgraflib1-dev_20061220+dfsg3-2_i386 +libgrafx11-1-dev_20061220+dfsg3-2_i386 +libgrantlee-dev_0.1.4-1_i386 +libgraphicsmagick++1-dev_1.3.16-1.1_i386 +libgraphicsmagick1-dev_1.3.16-1.1_i386 +libgraphite-dev_1:2.3.1-0.2_i386 +libgraphite2-dev_1.1.3-1_i386 +libgraphviz-dev_2.26.3-14+deb7u1_i386 +libgretl1-dev_1.9.9-1_i386 +libgrib-api-dev_1.9.16-2+b1_i386 +libgrib2c-dev_1.2.2-2+b1_i386 +libgridsite-dev_1.7.16-1_i386 +libgrilo-0.1-dev_0.1.19-1_i386 +libgringotts-dev_1.2.10~pre3-1_i386 +libgrits-dev_0.7-1_i386 +libgrok-dev_1.20110708.1-4_i386 +libgrss-dev_0.5.0-1_i386 +libgs-dev_9.05~dfsg-6.3+deb7u1_i386 +libgsasl7-dev_1.8.0-2_i386 +libgsecuredelete-dev_0.2-1_i386 +libgsf-1-dev_1.14.21-2.1_i386 +libgsf-gnome-1-dev_1.14.21-2.1_i386 +libgsl0-dev_1.15+dfsg.2-2_i386 +libgsm0710-dev_1.2.2-2_i386 +libgsm0710mux-dev_0.11.2-1.1_i386 +libgsm1-dev_1.0.13-4_i386 +libgsmme-dev_1.10-13.2_i386 +libgsnmp0-dev_0.3.0-1.1_i386 +libgsql-dev_0.2.2-1.2+b1_i386 +libgss-dev_1.0.2-1_i386 +libgssdp-1.0-dev_0.12.2.1-2_i386 +libgssglue-dev_0.4-2_i386 +libgst-dev_3.2.4-2_i386 +libgstbuzztard-dev_0.5.0-2+deb7u1_i386 +libgstreamer-ocaml-dev_0.1.0-3+b1_i386 +libgstreamer-plugins-bad0.10-dev_0.10.23-7.1+deb7u1_i386 +libgstreamer-plugins-base0.10-dev_0.10.36-1.1_i386 +libgstreamer0.10-cil-dev_0.9.2-4_all +libgstreamer0.10-dev_0.10.36-1.2_i386 +libgstrtspserver-0.10-dev_0.10.8-3_i386 +libgtest-dev_1.6.0-2_i386 +libgtextutils-dev_0.6.2-1_i386 +libgtg-dev_0.2+dfsg-1_i386 +libgtk-3-dev_3.4.2-7_i386 +libgtk-sharp-beans2.0-cil-dev_2.14.1-3_all +libgtk-vnc-1.0-dev_0.5.0-3.1_i386 +libgtk-vnc-2.0-dev_0.5.0-3.1_i386 +libgtk2.0-cil-dev_2.12.10-5_i386 +libgtk2.0-dev_2.24.10-2_i386 +libgtkada2.24.1-dev_2.24.1-7_i386 +libgtkdatabox-0.9.1-1-dev_1:0.9.1.1-4_i386 +libgtkgl2.0-dev_2.0.1-2_i386 +libgtkglada2.24.1-dev_2.24.1-7_i386 +libgtkglarea-cil-dev_0.0.17-6_all +libgtkglext1-dev_1.2.0-2_i386 +libgtkglextmm-x11-1.2-dev_1.2.0-4.1_i386 +libgtkhex-3-dev_3.4.1-1_i386 +libgtkhotkey-dev_0.2.1-3_i386 +libgtkhtml-4.0-dev_4.4.4-1_i386 +libgtkhtml-editor-3.14-dev_3.32.2-2.1_i386 +libgtkhtml-editor-4.0-dev_4.4.4-1_i386 +libgtkhtml3.14-cil-dev_2.26.0-8_i386 +libgtkhtml3.14-dev_3.32.2-2.1_i386 +libgtkimageview-dev_1.6.4+dfsg-0.1_i386 +libgtkmathview-dev_0.8.0-8_i386 +libgtkmm-2.4-dev_1:2.24.2-1_i386 +libgtkmm-3.0-dev_3.4.2-1_i386 +libgtkpod-dev_2.1.2-1_i386 +libgtksourceview-3.0-dev_3.4.2-1_i386 +libgtksourceview2-cil-dev_2.26.0-8_i386 +libgtksourceview2.0-dev_2.10.4-1_i386 +libgtksourceviewmm-3.0-dev_3.2.0-1_i386 +libgtkspell-3-dev_3.0.0~hg20110814-1_i386 +libgtkspell-dev_2.0.16-1_i386 +libgtop2-dev_2.28.4-3_i386 +libgts-dev_0.7.6+darcs110121-1.1_i386 +libguac-dev_0.6.0-2_i386 +libgucharmap-2-90-dev_1:3.4.1.1-2.1_i386 +libgudev-1.0-dev_175-7.2_i386 +libgudev1.0-cil-dev_0.1-3_all +libguess-dev_1.1-1_i386 +libguestfs-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-gobject-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-ocaml-dev_1:1.18.1-1+deb7u3_i386 +libguichan-dev_0.8.2-10+b1_i386 +libgupnp-1.0-dev_0.18.4-1_i386 +libgupnp-av-1.0-dev_0.10.3-1_i386 +libgupnp-dlna-1.0-dev_0.6.6-1_i386 +libgupnp-igd-1.0-dev_0.2.1-2_i386 +libgusb-dev_0.1.3-5_i386 +libgutenprint-dev_5.2.9-1_i386 +libgutenprintui2-dev_5.2.9-1_i386 +libguytools2-dev_2.0.1-1.1_i386 +libgvnc-1.0-dev_0.5.0-3.1_i386 +libgweather-3-dev_3.4.1-1+build1_i386 +libgwenhywfar60-dev_4.3.3-1_i386 +libgwrap-runtime-dev_1.9.14-1.1_i386 +libgwyddion20-dev_2.28-2_i386 +libgxps-dev_0.2.2-2_i386 +libgyoto0-dev_0.0.3-5_i386 +libh323plus-dev_1.24.0~dfsg2-1_i386 +libhaildb-dev_2.3.2-1.2_i386 +libhal-dev_0.5.14-8_i386 +libhal-storage-dev_0.5.14-8_i386 +libhamlib++-dev_1.2.15.1-1_i386 +libhamlib-dev_1.2.15.1-1_i386 +libhandoff-dev_0.1-5_i386 +libhangul-dev_0.1.0-2_i386 +libharminv-dev_1.3.1-9_i386 +libhashkit-dev_1.0.8-1_i386 +libhawknl-dev_1.6.8+dfsg2-1_i386 +libhbaapi-dev_2.2.5-1_i386 +libhbalinux-dev_1.0.14-1_i386 +libhd-dev_16.0-2.2_i386 +libhdate-dev_1.6-1_i386 +libhdf4-alt-dev_4.2r4-13_i386 +libhdf4-dev_4.2r4-13_i386 +libhdf4g-dev_4.2r4-13_all +libhdf5-dev_1.8.8-9_i386 +libhdf5-mpi-dev_1.8.8-9_i386 +libhdf5-mpich2-dev_1.8.8-9_i386 +libhdf5-openmpi-dev_1.8.8-9_i386 +libhdf5-serial-dev_1.8.8-9_i386 +libhdfeos-dev_2.17v1.00.dfsg.1-3_i386 +libhdhomerun-dev_20120405-1_i386 +libhe5-hdfeos-dev_5.1.13.dfsg.1-3_i386 +libheartbeat2-dev_1:3.0.5-3_i386 +libhepmc-dev_2.06.09-1_i386 +libhepmcfio-dev_2.06.09-1_i386 +libhepmcinterface8-dev_8.1.65-1_i386 +libherwig59-2-dev_20061220+dfsg3-2_i386 +libhesiod-dev_3.0.2-21_i386 +libhfsp-dev_1.0.4-12_i386 +libhighgui-dev_2.3.1-11_i386 +libhippocanvas-dev_0.3.1-1.1_i386 +libhiredis-dev_0.10.1-7_i386 +libhivex-dev_1.3.6-2_i386 +libhivex-ocaml-dev_1.3.6-2_i386 +libhkl-dev_4.0.3-4_i386 +libhmsbeagle-dev_1.0-6_i386 +libhocr-dev_0.10.17-1+b2_i386 +libhpdf-dev_2.2.1-1_i386 +libhpmud-dev_3.12.6-3.1+deb7u1_i386 +libhsclient-dev_1.1.0-7-g1044a28-1_i386 +libhtmlcxx-dev_0.85-2_i386 +libhtp-dev_0.2.6-2_i386 +libhtsengine-dev_1.06-1_i386 +libhttp-ocaml-dev_0.1.5-1+b2_i386 +libhttrack-dev_3.46.1-1_i386 +libhunspell-dev_1.3.2-4_i386 +libhwloc-dev_1.4.1-4_i386 +libhx-dev_3.12.1-1_i386 +libhyantes-dev_1.3.0-1_i386 +libhyena-cil-dev_0.5-2_all +libhyphen-dev_2.8.3-2_i386 +libhypre-dev_2.8.0b-1_all +libhz-dev_0.3.16-3_i386 +libi2c-dev_3.1.0-2_all +libibcm-dev_1.0.4-1.1_i386 +libibcommon-dev_1.1.2-20090314-1_i386 +libibdm-dev_1.2-OFED-1.4.2-1.3_i386 +libibmad-dev_1.2.3-20090314-1.1_i386 +libibtk-dev_0.0.14-12_i386 +libibumad-dev_1.2.3-20090314-1.1_i386 +libibus-1.0-dev_1.4.1-9+deb7u1_i386 +libibus-qt-dev_1.3.1-2.1_i386 +libibverbs-dev_1.1.6-1_i386 +libical-dev_0.48-2_i386 +libicapapi-dev_1:0.1.6-1.1_i386 +libicc-dev_2.12+argyll1.4.0-8_i386 +libicc-utils-dev_1.6.4-1+b1_i386 +libice-dev_2:1.0.8-2_i386 +libicee-dev_1.2.0-6.1_i386 +libicns-dev_0.8.1-1_i386 +libiconv-hook-dev_0.0.20021209-10_i386 +libics-dev_1.5.2-3_i386 +libicu-dev_4.8.1.1-12+deb7u1_i386 +libid3-3.8.3-dev_3.8.3-15_i386 +libid3tag0-dev_0.15.1b-10_i386 +libident-dev_0.22-3_i386 +libidl-dev_0.8.14-0.2_i386 +libidn11-dev_1.25-2_i386 +libidn2-0-dev_0.8-2_i386 +libido-0.1-dev_0.3.4-1_i386 +libido3-0.1-dev_0.3.4-1_i386 +libidzebra-2.0-dev_2.0.44-3_i386 +libiec16022-dev_0.2.4-1_i386 +libiec61883-dev_1.2.0-0.1_i386 +libieee1284-3-dev_0.2.11-10_i386 +libifp-dev_1.0.0.2-5_i386 +libifstat-dev_1.1-8_i386 +libigraph0-dev_0.5.4-2_i386 +libigstk4-dev_4.4.0-2+b1_i386 +libijs-dev_0.35-8_i386 +libiksemel-dev_1.2-4_i386 +libilmbase-dev_1.0.1-4_i386 +libimdi-dev_1.4.0-8_i386 +libiml-dev_1.0.3-4.2_i386 +libimlib2-dev_1.4.5-1_i386 +libimobiledevice-dev_1.1.1-4_i386 +libindi-dev_0.9.1-2_i386 +libindicate-dev_0.6.92-1_i386 +libindicate-gtk-dev_0.6.92-1_i386 +libindicate-gtk0.1-cil-dev_0.6.92-1_all +libindicate-gtk3-dev_0.6.92-1_i386 +libindicate-qt-dev_0.2.5.91-5_i386 +libindicate0.1-cil-dev_0.6.92-1_all +libindicator-dev_0.5.0-1_i386 +libindicator-messages-status-provider-dev_0.6.0-1_i386 +libindicator3-dev_0.5.0-1_i386 +libindigo-dev_1.0.0-2_i386 +libinfinity-0.5-dev_0.5.2-6.1_i386 +libini-config-dev_0.1.3-2_i386 +libinifiles-ocaml-dev_1.2-2_i386 +libinnodb-dev_1.0.6.6750-1_i386 +libinotify-ocaml-dev_1.0-1+b3_i386 +libinotifytools0-dev_3.14-1_i386 +libinput-pad-dev_1.0.1-2_i386 +libinsighttoolkit3-dev_3.20.1+git20120521-3_i386 +libinstpatch-dev_1.0.0-3_i386 +libint-dev_1.1.4-1_i386 +libiodbc2-dev_3.52.7-2+deb7u1_i386 +libion-dev_3.0.1~dfsg1-1_i386 +libipa-hbac-dev_1.8.4-2_i386 +libipathverbs-dev_1.2-1_i386 +libipe-dev_7.1.2-1_i386 +libipmiconsole-dev_1.1.5-3_i386 +libipmidetect-dev_1.1.5-3_i386 +libipmimonitoring-dev_1.1.5-3_i386 +libipset-dev_6.12.1-1_i386 +libiptcdata0-dev_1.0.4-3_i386 +libircclient-dev_1.3+dfsg1-3_i386 +libirman-dev_0.4.4-2_i386 +libirrlicht-dev_1.7.3+dfsg1-4_i386 +libisajet758-3-dev_20061220+dfsg3-2_i386 +libiscsi-dev_1.4.0-3_i386 +libisl-dev_0.10-3_i386 +libiso9660-dev_0.83-4_i386 +libisoburn-dev_1.2.2-2_i386 +libisofs-dev_1.2.2-1_i386 +libitl-dev_0.7.0-3_i386 +libitl-gobject-dev_0.2-1_i386 +libitpp-dev_4.2-4_i386 +libitsol-dev_1.0.0-2_i386 +libivykis-dev_0.30.1-2_i386 +libiw-dev_30~pre9-8_i386 +libjack-dev_1:0.121.3+20120418git75e3e20b-2.1_i386 +libjack-jackd2-dev_1.9.8~dfsg.4+20120529git007cdc37-5_i386 +libjalali-dev_0.4.0-1.1_i386 +libjama-dev_1.2.4-2_all +libjana-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-ecal-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-gtk-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjansson-dev_2.3.1-2_i386 +libjasper-dev_1.900.1-13_i386 +libjaula-dev_1.4.0-3_i386 +libjavascriptcoregtk-1.0-dev_1.8.1-3.4_i386 +libjavascriptcoregtk-3.0-dev_1.8.1-3.4_i386 +libjbig-dev_2.0-2+deb7u1_i386 +libjbig2dec0-dev_0.11+20120125-1_i386 +libjconv-dev_2.8-6+b1_i386 +libjemalloc-dev_3.0.0-3_i386 +libjim-dev_0.73-3_i386 +libjpeg62-dev_6b1-3_i386 +libjpeg8-dev_8d-1_i386 +libjpgalleg4-dev_2:4.4.2-2.1_i386 +libjs-of-ocaml-dev_1.2-2_i386 +libjson-glib-dev_0.14.2-1_i386 +libjson-spirit-dev_4.04-1+b1_i386 +libjson-static-camlp4-dev_0.9.8-1+b5_i386 +libjson-wheel-ocaml-dev_1.0.6-2+b8_i386 +libjson0-dev_0.10-1.2_i386 +libjsoncpp-dev_0.6.0~rc2-3_i386 +libjte-dev_1.19-1_i386 +libjthread-dev_1.3.1-3_i386 +libjudy-dev_1.0.5-1_i386 +libjuman-dev_5.1-2.1_i386 +libk3b-dev_2.0.2-6_i386 +libkactivities-dev_4:4.8.4-1_i386 +libkakasi2-dev_2.3.5~pre1+cvs20071101-1_i386 +libkal-dev_0.9.0-1_i386 +libkarma-cil-dev_0.1.2-2.3_all +libkarma-dev_0.1.2-2.3_i386 +libkate-dev_0.4.1-1_i386 +libkaya-gd-dev_0.4.4-6_i386 +libkaya-gl-dev_0.4.4-6_i386 +libkaya-mysql-dev_0.4.4-6_i386 +libkaya-ncurses-dev_0.4.4-6_i386 +libkaya-ncursesw-dev_0.4.4-6_i386 +libkaya-pgsql-dev_0.4.4-6_i386 +libkaya-sdl-dev_0.4.4-6_i386 +libkaya-sqlite3-dev_0.4.4-6_i386 +libkcddb-dev_4:4.8.4-2_i386 +libkdcraw-dev_4:4.8.4-1_i386 +libkdeedu-dev_4:4.8.4-1_i386 +libkdegames-dev_4:4.8.4-3_i386 +libkdtree++-dev_0.7.0-2_all +libkernlib1-dev_20061220+dfsg3-2_i386 +libkexiv2-dev_4:4.8.4-1_i386 +libkeybinder-dev_0.2.2-4_i386 +libkeyutils-dev_1.5.5-3_i386 +libkibi-dev_0.1-1_i386 +libkipi-dev_4:4.8.4-1_i386 +libkiten-dev_4:4.8.4-1_i386 +libklatexformula3-dev_3.2.6-1_i386 +libklibc-dev_2.0.1-3.1_i386 +libkmfl-dev_0.9.8-1_i386 +libkmflcomp-dev_0.9.8-1_i386 +libkml-dev_1.3.0~r863-4.1_i386 +libkmod-dev_9-3_i386 +libkokyu-dev_6.0.3+dfsg-0.1_i386 +libkonq5-dev_4:4.8.4-2_i386 +libkonqsidebarplugin-dev_4:4.8.4-2_i386 +libkopete-dev_4:4.8.4-1+b1_i386 +libkosd2-dev_0.8.1-1_i386 +libkpathsea-dev_2012.20120628-4_i386 +libkqueue-dev_1.0.4-2_i386 +libkrb5-dev_1.10.1+dfsg-5+deb7u1_i386 +libksane-dev_4:4.8.4-1_i386 +libksba-dev_1.2.0-2_i386 +libktoblzcheck1-dev_1.39-1_i386 +libktorrent-dev_1.2.1-1_i386 +libktpcommoninternalsprivate-dev_0.4.0-1_i386 +libkvutils-dev_2.9.0-1_i386 +libkvutils2.2-dev_2.9.0-1_all +libkwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libkwwidgets1-dev_1.0.0~cvs20100930-8_i386 +libkxl0-dev_1.1.7-16_i386 +liblablgl-ocaml-dev_1.04-5+b3_i386 +liblablgtk-extras-ocaml-dev_1.0-1+b2_i386 +liblablgtk2-gl-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-gnome-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtkmathview-ocaml-dev_0.7.8-6+b1_i386 +liblablgtksourceview2-ocaml-dev_2.14.2+dfsg-3_i386 +libladr-dev_0.0.200902a-2.1_i386 +libladspa-ocaml-dev_0.1.4-1+b1_i386 +liblapack-dev_3.4.1+dfsg-1+deb70u1_i386 +liblapacke-dev_3.4.1+dfsg-1+deb70u1_i386 +liblas-dev_1.2.1-5+b1_i386 +liblash-compat-dev_1+dfsg0-3_all +liblasi-dev_1.1.0-1_i386 +liblasso3-dev_2.3.6-2_i386 +liblastfm-dev_0.4.0~git20090710-2_i386 +liblastfm-ocaml-dev_0.3.0-2+b6_i386 +liblcgdm-dev_1.8.2-1+b2_i386 +liblcms1-dev_1.19.dfsg-1.2_i386 +liblcms2-dev_2.2+git20110628-2.2+deb7u1_i386 +libldap-ocaml-dev_2.1.8-8+b9_i386 +libldap2-dev_2.4.31-1+nmu2_i386 +libldb-dev_1:1.1.6-1_i386 +libldns-dev_1.6.13-1_i386 +libledit-ocaml-dev_2.03-1+b2_i386 +liblensfun-dev_0.2.5-2_i386 +libleptonica-dev_1.69-3.1_i386 +libleveldb-dev_0+20120530.gitdd0d562-1_i386 +liblfc-dev_1.8.2-1+b2_i386 +liblhapdf-dev_5.8.7+repack-1_i386 +liblhasa-dev_0.0.7-2_i386 +liblicense-dev_0.8.1-3_i386 +liblightdm-gobject-dev_1.2.2-4_i386 +liblightdm-qt-dev_1.2.2-4_i386 +liblilv-dev_0.14.2~dfsg0-4_i386 +liblinear-dev_1.8+dfsg-1_i386 +liblinebreak2-dev_2.1-1_i386 +liblink-grammar4-dev_4.7.4-2_i386 +liblinphone-dev_3.5.2-10_i386 +liblip-dev_2.0.0-1.1_i386 +liblircclient-dev_0.9.0~pre1-1_i386 +liblistaller-glib-dev_0.5.5-2_i386 +liblivemedia-dev_2012.05.17-1_i386 +libllvm-2.9-ocaml-dev_2.9+dfsg-7_i386 +libllvm-3.0-ocaml-dev_3.0-10_i386 +libllvm-3.1-ocaml-dev_3.1-1_i386 +libllvm-ocaml-dev_1:3.0-14+nmu2_i386 +liblo-dev_0.26~repack-7_i386 +liblo-ocaml-dev_0.1.0-1+b1_i386 +liblo10k1-dev_1.0.25-2_i386 +libloadpng4-dev_2:4.4.2-2.1_i386 +liblockdev1-dev_1.0.3-1.5_i386 +liblockfile-dev_1.09-5_i386 +liblodo3.0-dev_3.0.2+dfsg-4+b1_i386 +liblog4ada2-dev_1.2-3_i386 +liblog4c-dev_1.2.1-3_i386 +liblog4cplus-dev_1.0.4-1_i386 +liblog4cpp5-dev_1.0-4_i386 +liblog4cxx10-dev_0.10.0-1.2_i386 +liblog4net-cil-dev_1.2.10+dfsg-6_all +liblog4shib-dev_1.0.4-1_i386 +liblog4tango4-dev_7.2.6+dfsg-14_i386 +liblogforwarderutils2-dev_2.7-1_i386 +liblognorm-dev_0.3.4-1_i386 +liblogservicecomponentbase2-dev_2.7-1_i386 +liblogservicetoolbase2-dev_2.7-1_i386 +liblogsys-dev_1.4.2-3_i386 +liblogthread-dev_3.0.12-3.2+deb7u2_i386 +libloki-dev_0.1.7-3_i386 +libloudmouth1-dev_1.4.3-9_i386 +liblouis-dev_2.4.1-1_i386 +liblouisutdml-dev_2.2.0-1_i386 +liblouisxml-dev_2.4.0-3_i386 +liblowpan-dev_0.2.2-2.1_all +liblpsolve55-dev_5.5.0.13-7_i386 +liblqr-1-0-dev_0.4.1-2_i386 +liblrdf0-dev_0.4.0-5_i386 +liblrm2-dev_1.0.9+hg2665-1_i386 +liblrs-dev_0.42c-1+b1_i386 +liblscp-dev_0.5.6-6_i386 +libltcsmpte-dev_0.4.4-1_i386 +libltdl-dev_2.4.2-1.1_i386 +liblttctl-dev_0.89-05122011-1_i386 +liblttd-dev_0.89-05122011-1_i386 +liblttng-ust-dev_2.0.4-1_i386 +liblttoolbox3-3.1-0-dev_3.1.0-1.1_i386 +liblttvtraceread-2.6-dev_0.12.38-21032011-1+b1_i386 +liblua5.1-0-dev_5.1.5-4_i386 +liblua5.1-apr-dev_0.23.2-1_all +liblua5.1-bitop-dev_1.0.2-1_all +liblua5.1-cgi-dev_5.1.4+dfsg-2_all +liblua5.1-copas-dev_1.1.6-5_all +liblua5.1-curl-dev_0.3.0-7_all +liblua5.1-cyrussasl-dev_1.0.0-4_all +liblua5.1-event-dev_0.4.1-2_all +liblua5.1-expat-dev_1.2.0-5+deb7u1_all +liblua5.1-filesystem-dev_1.5.0+16+g84f1af5-1_all +liblua5.1-leg-dev_0.1.2-8_all +liblua5.1-logging-dev_1.2.0-1_all +liblua5.1-lpeg-dev_0.10.2-5_all +liblua5.1-md5-dev_1.1.2-6_all +liblua5.1-oocairo-dev_1.4-1.2_i386 +liblua5.1-oopango-dev_1.1-1_i386 +liblua5.1-orbit-dev_2.2.0+dfsg1-1_all +liblua5.1-posix-dev_5.1.19-2_all +liblua5.1-rex-onig-dev_2.6.0-2_all +liblua5.1-rex-pcre-dev_2.6.0-2_all +liblua5.1-rex-posix-dev_2.6.0-2_all +liblua5.1-rings-dev_1.2.3-1_all +liblua5.1-rrd-dev_1.4.7-2_i386 +liblua5.1-sec-dev_0.4.1-1_all +liblua5.1-soap-dev_3.0-3_all +liblua5.1-socket-dev_2.0.2-8_all +liblua5.1-sql-mysql-dev_2.3.0-1+build0_all +liblua5.1-sql-postgres-dev_2.3.0-1+build0_all +liblua5.1-sql-sqlite3-dev_2.3.0-1+build0_all +liblua5.1-svn-dev_0.4.0-7_all +liblua5.1-wsapi-fcgi-dev_1.5-3_all +liblua5.1-xmlrpc-dev_1.2.1-5_all +liblua5.1-zip-dev_1.2.3-11_all +liblua5.2-dev_5.2.1-3_i386 +liblua50-dev_5.0.3-6_i386 +libluabind-dev_0.9.1+dfsg-5_i386 +liblualib50-dev_5.0.3-6_i386 +liblunar-1-dev_2.0.1-2.2_i386 +liblunar-date-dev_2.4.0-1_i386 +liblv2dynparam1-dev_2-5_i386 +liblvm2-dev_2.02.95-8_i386 +liblwipv6-dev_1.5a-2_i386 +liblwt-glib-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ssl-ocaml-dev_2.3.2-1+b3_i386 +liblz-dev_1.3-2_i386 +liblzma-dev_5.1.1alpha+20120614-2_i386 +liblzo2-dev_2.06-1_i386 +libm17n-dev_1.6.3-2_i386 +libm17n-im-config-dev_0.9.0-3_i386 +libm4ri-dev_0.0.20080521-2_i386 +libmaa-dev_1.3.1-1_i386 +libmad-ocaml-dev_0.4.4-1+b1_i386 +libmad0-dev_0.15.1b-7_i386 +libmadlib-dev_1.3.0-2.1_i386 +libmagic-dev_5.11-2+deb7u3_i386 +libmagic-ocaml-dev_0.7.3-5+b3_i386 +libmagick++-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickcore-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickwand-dev_8:6.7.7.10-5+deb7u3_i386 +libmagics++-dev_2.14.11-4_i386 +libmailutils-dev_1:2.99.97-3_i386 +libmalaga-dev_7.12-4_i386 +libmaloc-dev_0.2-2.3_i386 +libmapi-dev_1:1.0-3_i386 +libmapiadmin-dev_1:1.0-3_i386 +libmapipp-dev_1:1.0-3_i386 +libmapiproxy-dev_1:1.0-3_i386 +libmapistore-dev_1:1.0-3_i386 +libmapnik-dev_2.0.0+ds1-3_all +libmapnik2-dev_2.0.0+ds1-3+b4_i386 +libmarble-dev_4:4.8.4-3_i386 +libmarkdown2-dev_2.1.3-3_i386 +libmatchbox-dev_1.9-osso8-3_i386 +libmath++-dev_0.0.4-4_i386 +libmatheval-dev_1.1.8-1_i386 +libmathlib2-dev_20061220+dfsg3-2_i386 +libmatio-dev_1.3.4-4_i386 +libmatrixssl1.8-dev_1.8.8-1_i386 +libmatroska-dev_1.3.0-2_i386 +libmbt0-dev_3.2.8-1_i386 +libmcpp-dev_2.7.2-1.1_i386 +libmcrypt-dev_2.5.8-3.1_i386 +libmcs-dev_0.7.2-2.1_i386 +libmd3-dev_0.1.92-4_i386 +libmdc2-dev_0.10.7-1+b2_i386 +libmdds-dev_0.5.4-1_all +libmdsp-dev_0.11-10_i386 +libmeanwhile-dev_1.0.2-4_i386 +libmecab-dev_0.99.3-3_i386 +libmed-dev_3.0.3-3_i386 +libmedc-dev_3.0.3-3_i386 +libmediainfo-dev_0.7.58-1_i386 +libmediastreamer-dev_3.5.2-10_i386 +libmedimport-dev_3.0.3-3_i386 +libmeep-dev_1.1.1-8+deb7u1_i386 +libmeep-lam4-dev_1.1.1-10~deb7u1_i386 +libmeep-mpi-default-dev_1.1.1-10~deb7u1_i386 +libmeep-mpich2-dev_1.1.1-10~deb7u1_i386 +libmeep-openmpi-dev_1.1.1-9~deb7u2_i386 +libmelt-ocaml-dev_1.4.0-1_i386 +libmemcache-dev_1.4.0.rc2-1_i386 +libmemcached-dev_1.0.8-1_i386 +libmemphis-0.2-dev_0.2.3-2_i386 +libmenhir-ocaml-dev_20120123.dfsg-1_i386 +libmenu-cache1-dev_0.3.3-1_i386 +libmercator-0.3-dev_0.3.0-2_i386 +libmeschach-dev_1.2b-13_i386 +libmetacity-dev_1:2.34.3-4_i386 +libmgl-dev_1.11.2-17_i386 +libmhash-dev_0.9.9.9-1.1_i386 +libmicrohttpd-dev_0.9.20-1+deb7u1_i386 +libmigemo-dev_20110227-7_i386 +libmikmatch-ocaml-dev_1.0.4-1+b1_i386 +libmikmod2-dev_3.1.12-5_i386 +libmilter-dev_8.14.4-4_i386 +libmimedir-dev_0.5.1-4_i386 +libmimedir-gnome-dev_0.4.2-5_i386 +libmimelib1-dev_5:1.1.4-2_i386 +libmimetic-dev_0.9.7-3_i386 +libmimic-dev_1.0.4-2.1_i386 +libminc-dev_2.1.10-1+b1_i386 +libming-dev_1:0.4.4-1.1_i386 +libmini18n-dev_0.2.1-1_i386 +libminidjvu-dev_0.8.svn.2010.05.06+dfsg-0.2_i386 +libminiupnpc-dev_1.5-2_i386 +libmission-control-plugins-dev_1:5.12.3-1_i386 +libmkv-dev_0.6.5.1-1_i386 +libmlpcap-ocaml-dev_0.9-16_i386 +libmlpost-ocaml-dev_0.8.1-3_i386 +libmlt++-dev_0.8.0-4_i386 +libmlt-dev_0.8.0-4_i386 +libmlx4-dev_1.0.4-1_i386 +libmm-dev_1.4.2-4_i386 +libmm-ocaml-dev_0.2.0-1+b1_i386 +libmmpong0.9-dev_0.9.1-2.1_i386 +libmms-dev_0.6.2-3_i386 +libmng-dev_1.0.10-3_i386 +libmnl-dev_1.0.3-3_i386 +libmodbus-dev_3.0.3-1_i386 +libmodglue1-dev_1.17-2.1_all +libmodplug-dev_1:0.8.8.4-3+deb7u1+git20130828_all +libmoe-dev_1.5.8-1_i386 +libmongo-client-dev_0.1.5-1+deb7u1_i386 +libmono-2.0-dev_2.10.8.1-8_i386 +libmono-addins-cil-dev_0.6.2-2_all +libmono-addins-gui-cil-dev_0.6.2-2_all +libmono-addins-msbuild-cil-dev_0.6.2-2_all +libmono-cecil-cil-dev_0.9.5+dfsg-2_all +libmono-cecil-flowanalysis-cil-dev_0.1~vcs20110809.r1.b34edf6-2_all +libmono-cil-dev_2.10.8.1-8_all +libmono-reflection-cil-dev_1.0+git20110407+d2343843-2_all +libmono-uia-cil-dev_2.1-4_all +libmono-upnp-cil-dev_0.1.2-1_all +libmono-zeroconf-cil-dev_0.9.0-4_all +libmonogame-cil-dev_2.5.1+dfsg-3_all +libmopac7-dev_1.15-5_i386 +libmorph-dev_1:20090926_i386 +libmosquitto0-dev_0.15-2_all +libmosquittopp0-dev_0.15-2_all +libmount-dev_2.20.1-5.3_i386 +libmowgli-dev_1.0.0-1_i386 +libmozjs-dev_24.4.0esr-1~deb7u2_i386 +libmozjs185-dev_1.8.5-1.0.0+dfsg-4_i386 +libmp3lame-dev_3.99.5+repack1-3_i386 +libmp3lame-ocaml-dev_0.3.1-1+b1_i386 +libmp3splt-dev_0.7.2-2_i386 +libmp4v2-dev_2.0.0~dfsg0-1_i386 +libmpc-dev_0.9-4_i386 +libmpcdec-dev_2:0.1~r459-4_i386 +libmpd-dev_0.20.0-1.1_i386 +libmpdclient-dev_2.3-1_i386 +libmpeg2-4-dev_0.4.1-3_i386 +libmpeg3-dev_1.5.4-5_i386 +libmpfi-dev_1.5.1-1_i386 +libmpfr-dev_3.1.0-5_i386 +libmpg123-dev_1.14.4-1_i386 +libmpich2-dev_1.4.1-4.2_i386 +libmpikmeans-dev_1.5-1+b1_i386 +libmrml1-dev_0.1.14-12_i386 +libmrmpi-dev_1.0~20110620.dfsg-2_i386 +libmrss0-dev_0.19.2-3_i386 +libmsgpack-dev_0.5.7-2_i386 +libmsn-dev_4.2-2_i386 +libmsv-dev_0.0.0-1_i386 +libmtbl-dev_0.2-1_i386 +libmtcp-dev_1.2.5-1_i386 +libmtdev-dev_1.1.2-1_i386 +libmthca-dev_1.0.6-1_i386 +libmtp-dev_1.1.3-35-g0ece104-5_i386 +libmudflap0-4.4-dev_4.4.7-2_i386 +libmudflap0-4.6-dev_4.6.3-14_i386 +libmudflap0-4.7-dev_4.7.2-5_i386 +libmulticobex1-dev_0.23-1.1_i386 +libmumps-dev_4.10.0.dfsg-3_i386 +libmumps-ptscotch-dev_4.10.0.dfsg-3_i386 +libmumps-scotch-dev_4.10.0.dfsg-3_i386 +libmumps-seq-dev_4.10.0.dfsg-3_i386 +libmunge-dev_0.5.10-1_i386 +libmuparser-dev_2.1.0-3_i386 +libmupdf-dev_0.9-2_i386 +libmupen64plus-dev_1.99.5-6_all +libmuroar-dev_0.1.8-2_i386 +libmusic-dev_1.0.7-1.2_i386 +libmusicbrainz3-dev_3.0.2-2.1_i386 +libmusicbrainz5-dev_5.0.1-2_i386 +libmutter-dev_3.4.1-5_i386 +libmx-dev_1.4.6-1_i386 +libmxml-dev_2.6-2_i386 +libmyproxy-dev_5.6-1_i386 +libmysql++-dev_3.1.0-2+b1_i386 +libmysql-cil-dev_6.4.3-2_all +libmysql-ocaml-dev_1.1.1-1_i386 +libmysqlclient-dev_5.5.35+dfsg-0+wheezy1_i386 +libmysqlcppconn-dev_1.1.0-4+b1_i386 +libmysqld-dev_5.5.35+dfsg-0+wheezy1_i386 +libmythes-dev_2:1.2.2-1_i386 +libnabrit-dev_0.4.1-1_i386 +libnacl-dev_20110221-4_i386 +libnacore-dev_0.4.0-3_i386 +libnanohttp-dev_1.1.0-17.1_i386 +libnatpmp-dev_20110808-3_i386 +libnautilus-extension-dev_3.4.2-1+build1_i386 +libnbio-dev_0.30-1_i386 +libncap-dev_1.9.2-1+b2_i386 +libncbi6-dev_6.1.20120620-2_i386 +libncp-dev_2.2.6-9_i386 +libncurses5-dev_5.9-10_i386 +libncursesada2-dev_5.9.20110404-7_i386 +libncursesw5-dev_5.9-10_i386 +libndesk-dbus-glib1.0-cil-dev_0.4.1-4_all +libndesk-dbus1.0-cil-dev_0.6.0-6_all +libndr-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libndr-standard-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libnecpp-dev_1.5.0+cvs20101003-2.1_i386 +libneon27-dev_0.29.6-3_i386 +libneon27-gnutls-dev_0.29.6-3_i386 +libnes-dev_1.1.3-1_i386 +libnet1-dev_1.1.4-2.1_i386 +libnet6-1.3-dev_1:1.3.14-1_i386 +libnetcdf-dev_1:4.1.3-6+b1_i386 +libnetcf-dev_0.1.9-2_i386 +libnetclasses-dev_1.06.dfsg-5+b3_i386 +libnetfilter-conntrack-dev_1.0.1-1_i386 +libnetfilter-cttimeout-dev_1.0.0-1_i386 +libnetfilter-log-dev_1.0.0-1_i386 +libnetfilter-queue-dev_0.0.17-1_i386 +libnethttpd-ocaml-dev_3.5.1-1_i386 +libnetpbm10-dev_2:10.0-15+b1_i386 +libnetpbm9-dev_2:10.0-15+b1_i386 +libnetsvcs-dev_6.0.3+dfsg-0.1_i386 +libnewlib-dev_1.18.0-6.2_i386 +libnewmat10-dev_1.10.4-5_i386 +libnewt-dev_0.52.14-11.1_i386 +libnewtonsoft-json-cil-dev_4.5r6-1_all +libnexus0-dev_4.2.1-svn1614-1+b2_i386 +libnfnetlink-dev_1.0.0-1.1_i386 +libnfo-dev_1.0.1-1_i386 +libnfs-dev_1.3.0-2_i386 +libnfsidmap-dev_0.25-4_i386 +libnice-dev_0.1.2-1_i386 +libnids-dev_1.23-2_i386 +libnifti-dev_2.0.0-1_i386 +libnih-dbus-dev_1.0.3-4.1_i386 +libnih-dev_1.0.3-4.1_i386 +libnini-cil-dev_1.1.0+dfsg.2-4_all +libnjb-dev_2.2.7~dfsg0-3_i386 +libnl-3-dev_3.2.7-4_i386 +libnl-cli-3-dev_3.2.7-4_i386 +libnl-dev_1.1-7_i386 +libnl-genl-3-dev_3.2.7-4_i386 +libnl-nf-3-dev_3.2.7-4_i386 +libnl-route-3-dev_3.2.7-4_i386 +libnm-glib-dev_0.9.4.0-10_i386 +libnm-glib-vpn-dev_0.9.4.0-10_i386 +libnm-gtk-dev_0.9.4.1-5_i386 +libnm-util-dev_0.9.4.0-10_i386 +libnmz7-dev_2.0.21-6_i386 +libnoise-dev_1.0.0+nmu1_i386 +libnotify-cil-dev_0.4.0~r3032-6_all +libnotify-dev_0.7.5-1_i386 +libnotmuch-dev_0.13.2-1_i386 +libnova-dev_0.14.0-2_i386 +libnpth0-dev_0.90-2_i386 +libnsbmp0-dev_0.0.1-1.1_i386 +libnsgif0-dev_0.0.1-1.1_i386 +libnspr4-dev_2:4.9.2-1+deb7u1_i386 +libnss3-dev_2:3.14.5-1_i386 +libntfs-dev_2.0.0-1+b1_i386 +libntl-dev_5.5.2-2_i386 +libntlm0-dev_1.2-1_i386 +libntrack-dev_016-1.1_i386 +libntrack-glib-dev_016-1.1_i386 +libntrack-gobject-dev_016-1.1_i386 +libntrack-qt4-dev_016-1.1_i386 +libnuclient-dev_2.4.3-2.2_i386 +libnuma-dev_2.0.8~rc4-1_i386 +libnunit-cil-dev_2.6.0.12051+dfsg-2_all +libnussl-dev_2.4.3-2.2_i386 +libnvtt-dev_2.0.8-1+dfsg-2_i386 +libnxcl-dev_0.9-3.1_i386 +libnxml0-dev_0.18.3-4_i386 +libnzb-dev_0.0.20050629-6.1_i386 +liboasis-ocaml-dev_0.2.0-6_i386 +liboasis3-dev_3.3.beta.dfsg.1-8+b1_i386 +liboath-dev_1.12.4-1_i386 +liboauth-dev_0.9.4-3.1_i386 +libobby-0.4-dev_0.4.8-1_i386 +libobexftp0-dev_0.23-1.1_i386 +libobrowser-ocaml-dev_1.1.1+dfsg-1+b9_i386 +libobus-ocaml-dev_1.1.3-1+b8_i386 +libocamlbricks-ocaml-dev_0.50.1-4+b5_i386 +libocamlgraph-ocaml-dev_1.8.2-2_i386 +libocamlgraph-viewer-ocaml-dev_1.8.2-2_i386 +libocamlgsl-ocaml-dev_0.6.0-7+b2_i386 +libocamlnet-gtk2-ocaml-dev_3.5.1-1_i386 +libocamlnet-ocaml-dev_3.5.1-1_i386 +libocamlnet-ssl-ocaml-dev_3.5.1-1_i386 +libocamlodbc-ocaml-dev_2.15-5+b3_i386 +libocamlviz-ocaml-dev_1.01-2+b2_i386 +libocas-dev_0.93-1_i386 +liboce-foundation-dev_0.9.1-3_i386 +liboce-modeling-dev_0.9.1-3_all +liboce-ocaf-dev_0.9.1-3_all +liboce-ocaf-lite-dev_0.9.1-3_all +liboce-visualization-dev_0.9.1-3_all +libocpf-dev_1:1.0-3_i386 +libocrad-dev_0.22~rc1-2_i386 +libocsigen-ocaml-dev_1.3.4-2+b12_i386 +libocsigen-xhtml-ocaml-dev_1.3.4-2+b12_i386 +libocsigenserver-ocaml-dev_2.1-1_i386 +liboctave-dev_3.6.2-5+deb7u1_i386 +libode-dev_2:0.11.1-4_i386 +libode-sp-dev_2:0.11.1-4_i386 +libodin-dev_1.8.5-2_i386 +libodn-ocaml-dev_0.0.8-1_i386 +libofa0-dev_0.9.3-5_i386 +libofapi-dev_0git20070620-6_i386 +libofdt-dev_1.3.6-1_i386 +libofetion-dev_2.2.2-1_i386 +libofx-dev_1:0.9.4-2.1_i386 +libogdi3.2-dev_3.2.0~beta2-7_i386 +libogg-dev_1.3.0-4_i386 +libogg-ocaml-dev_0.4.3-1+b1_i386 +liboggkate-dev_0.4.1-1_i386 +liboggplay1-dev_0.2.1~git20091227-1.2_i386 +liboggz2-dev_1.1.1-1_i386 +liboglappth-dev_1.0.0-2_i386 +libogre-1.8-dev_1.8.0+dfsg1-3_i386 +libogre-dev_1.7.4+dfsg1-7_i386 +liboil0.3-dev_0.3.17-2_i386 +libois-dev_1.3.0+dfsg0-5_i386 +libomhacks-dev_0.16-1_i386 +libomnievents-dev_1:2.6.2-2_i386 +libomniorb4-dev_4.1.6-2_i386 +libomnithread3-dev_4.1.6-2_i386 +libomxil-bellagio-dev_0.9.3-1+b1_i386 +libonig-dev_5.9.1-1_i386 +liboobs-1-dev_3.0.0-1_i386 +liboop-dev_1.0-9_i386 +libooptools-dev_2.7-1_i386 +libopal-dev_3.10.4~dfsg-3_i386 +libopenafs-dev_1.6.1-3+deb7u2_i386 +libopenais-dev_1.1.4-4.1_i386 +libopenal-dev_1:1.14-4_i386 +libopenbabel-dev_2.3.1+dfsg-4_i386 +libopenblas-dev_0.1.1-6+deb7u3_i386 +libopencc-dev_0.3.0-3_i386 +libopenconnect-dev_3.20-4_i386 +libopencore-amrnb-dev_0.1.3-2_i386 +libopencore-amrwb-dev_0.1.3-2_i386 +libopencryptoki-dev_2.3.1+dfsg-3_i386 +libopencsg-dev_1.3.2-2_i386 +libopenct1-dev_0.6.20-1.2_i386 +libopencv-calib3d-dev_2.3.1-11_i386 +libopencv-contrib-dev_2.3.1-11_i386 +libopencv-core-dev_2.3.1-11_i386 +libopencv-dev_2.3.1-11_i386 +libopencv-features2d-dev_2.3.1-11_i386 +libopencv-flann-dev_2.3.1-11_i386 +libopencv-gpu-dev_2.3.1-11_i386 +libopencv-highgui-dev_2.3.1-11_i386 +libopencv-imgproc-dev_2.3.1-11_i386 +libopencv-legacy-dev_2.3.1-11_i386 +libopencv-ml-dev_2.3.1-11_i386 +libopencv-objdetect-dev_2.3.1-11_i386 +libopencv-video-dev_2.3.1-11_i386 +libopendkim-dev_2.6.8-4_i386 +libopenexr-dev_1.6.1-6_i386 +libopenhpi-dev_2.14.1-1.2_i386 +libopenigtlink1-dev_1.9.2~svn7468-1_i386 +libopenimageio-dev_1.0.5+dfsg0-1_i386 +libopenipmi-dev_2.0.16-1.3_i386 +libopenjpeg-dev_1.3+dfsg-4.7_i386 +libopenmeeg-dev_2.0.0.dfsg-5_i386 +libopenmpi-dev_1.4.5-1_i386 +libopenobex1-dev_1.5-2_i386 +libopenr2-dev_1.3.2-1.1_i386 +libopenraw-dev_0.0.9-3+b1_i386 +libopenrawgnome-dev_0.0.9-3+b1_i386 +libopenscap-dev_0.8.0-4+b1_i386 +libopenscenegraph-dev_3.0.1-4_i386 +libopenslide-dev_3.2.6-2_i386 +libopensm2-dev_3.2.6-20090317-2.1_i386 +libopenthreads-dev_3.0.1-4_i386 +libopentk-cil-dev_1.0.20101006+dfsg1-1_all +libopentoken3-dev_4.0b-3_i386 +libopenturns-dev_1.0-4_i386 +libopenusb-dev_1.1.0-2_i386 +libopenvg1-mesa-dev_8.0.5-4+deb7u2_i386 +libopenvrml-dev_0.18.9-5+deb7u1_i386 +libopenwalnut1-dev_1.2.5-1.1+b1_i386 +liboping-dev_1.6.2-1_i386 +libopkele-dev_2.0.4-5.3_i386 +libopts25-dev_1:5.12-0.1_i386 +libopus-dev_0.9.14+20120615-1+nmu1_i386 +liborange-dev_0.4-2_i386 +liborbit2-dev_1:2.14.19-0.1_i386 +liborc-0.4-dev_1:0.4.16-2_i386 +liborigin-dev_20080225-2.1_i386 +liborigin2-dev_2:20110117-1+b2_i386 +libortp-dev_3.5.2-10_i386 +liboscpack-dev_1.0.2-1_i386 +libosgearth-dev_2.0+dfsg-4+b3_i386 +libosinfo-1.0-dev_0.1.1-1_i386 +libosip2-dev_3.6.0-4_i386 +libosl-dev_0.5.0-1_i386 +libosmesa6-dev_8.0.5-4+deb7u2_i386 +libosmgpsmap-dev_0.7.3-3_i386 +libosmium-dev_0.0~20111213-g7f3500a-3+b2_i386 +libosmpbf-dev_1.2.1-3_i386 +libosp-dev_1.5.2-10_i386 +libosptk3-dev_3.4.2-1+b1_i386 +libossim-dev_1.7.21-4_i386 +libossp-sa-dev_1.2.6-1_i386 +libossp-uuid-dev_1.6.2-1.3_i386 +libostyle-dev_1.4devel1-20.1+b1_i386 +libotcl1-dev_1.14+dfsg-2_i386 +libotf-dev_0.9.12-2_i386 +libotf-trace-dev_1.10.2+dfsg-2_i386 +libotpw-dev_1.3-2_i386 +libotr2-dev_3.2.1-1+deb7u1_i386 +libots-dev_0.5.0-2.1_i386 +libounit-ocaml-dev_1.1.1-1_i386 +libow-dev_2.8p15-1_i386 +libowfat-dev_0.28-6_i386 +libowfat-dietlibc-dev_0.28-6_i386 +libownet-dev_2.8p15-1_i386 +libp11-dev_0.2.8-2_i386 +libp11-kit-dev_0.12-3_i386 +libpackagekit-glib2-dev_0.7.6-3_i386 +libpackagekit-qt2-dev_0.7.6-3_i386 +libpacketdump3-dev_3.0.14-1_i386 +libpacklib-lesstif1-dev_20061220+dfsg3-2_i386 +libpacklib1-dev_20061220+dfsg3-2_i386 +libpacparser-dev_1.3.0-2_i386 +libpam-ocaml-dev_1.1-4+b3_i386 +libpam0g-dev_1.1.3-7.1_i386 +libpanel-applet-4-dev_3.4.2.1-4_i386 +libpango1.0-dev_1.30.0-1_i386 +libpangomm-1.4-dev_2.28.4-1_i386 +libpano13-dev_2.9.18+dfsg-5_i386 +libpantomime1.2-dev_1.2.0~pre3+snap20071004+dfsg-4+b1_i386 +libpaper-dev_1.1.24+nmu2_i386 +libpaps-dev_0.6.8-6_i386 +libpaq-dev_1.0.4-3+b1_i386 +libpar2-0-dev_0.2.1-1_i386 +libpari-dev_2.5.1-2_i386 +libparpack2-dev_3.1.1-2.1_i386 +libparrot-dev_4.0.0-3_i386 +libparser++-dev_0.2.3-2_all +libparted0-dev_2.3-12_i386 +libpasswdqc-dev_1.2.0-1_all +libpath-utils-dev_0.1.3-2_i386 +libpathfinder-dev_1.1.3-0.4+b1_i386 +libpawlib-lesstif3-dev_1:2.14.04.dfsg.2-8_i386 +libpawlib2-dev_1:2.14.04.dfsg.2-8_i386 +libpcap-dev_1.3.0-1_all +libpcap0.8-dev_1.3.0-1_i386 +libpcapnav0-dev_0.8-1_i386 +libpci-dev_1:3.1.9-6_i386 +libpciaccess-dev_0.13.1-2_i386 +libpcl1-dev_1.6-1_i386 +libpcre++-dev_0.9.5-5.1_i386 +libpcre-ocaml-dev_6.2.5-1_i386 +libpcre3-dev_1:8.30-5_i386 +libpcscada2-dev_0.7.1-4_i386 +libpcsclite-dev_1.8.4-1+deb7u1_i386 +libpdflib804-2-dev_20061220+dfsg3-2_i386 +libpe-rules2-dev_1.1.7-1_i386 +libpe-status3-dev_1.1.7-1_i386 +libpeas-dev_1.4.0-2_i386 +libpengine3-dev_1.1.7-1_i386 +libperl-dev_5.14.2-21+deb7u1_i386 +libperl4caml-ocaml-dev_0.9.5-4+b4_i386 +libpetsc3.2-dev_3.2.dfsg-6_i386 +libpfqueue-dev_0.5.6-8_i386 +libpfs-dev_1.8.5-1_i386 +libpgm-dev_5.1.118-1~dfsg-0.1_i386 +libpgocaml-ocaml-dev_1.5-2_i386 +libpgpool-dev_3.1.3-5_i386 +libpgtcl-dev_1:1.5-6_i386 +libphash0-dev_0.9.4-1.2_i386 +libphat-dev_0.4.1-5_i386 +libphobos-4.4-dev_1.063-4.4.7-1_i386 +libphobos2-4.6-dev_0.29.1-4.6.3-2_i386 +libphone-ui-dev_1:0.0.1+git20110825-3_i386 +libphone-utils-dev_0.1+git20110523-2.1_i386 +libphonon-dev_4:4.6.0.0-3_i386 +libphononexperimental-dev_4:4.6.0.0-3_i386 +libphotos202-dev_20061220+dfsg3-2_i386 +libphtools2-dev_20061220+dfsg3-2_i386 +libphysfs-dev_2.0.2-6_i386 +libpiano-dev_2012.05.06-2_i386 +libpigment0.3-dev_0.3.17-1_i386 +libpils2-dev_1.0.9+hg2665-1_i386 +libpinyin0-dev_0.6.91-1_i386 +libpion-common-dev_4.0.7+dfsg-3.1_i386 +libpion-net-dev_4.0.7+dfsg-3.1_i386 +libpipeline-dev_1.2.1-1_i386 +libpisock-dev_0.12.5-5_i386 +libpixman-1-dev_0.26.0-4+deb7u1_i386 +libpkcs11-helper1-dev_1.09-1_i386 +libplayer-dev_2.0.1-2.1_i386 +libplayerc++3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerc3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercommon3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercore3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerdrivers3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerinterface3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerjpeg3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayertcp3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerwkb3.0-dev_3.0.2+dfsg-4+b1_i386 +libplib-dev_1.8.5-6_i386 +libplist++-dev_1.8-1_i386 +libplist-dev_1.8-1_i386 +libpload-dev_1.4.2-3_i386 +libplot-dev_2.6-3_i386 +libploticus0-dev_2.41-5_i386 +libplotmm-dev_0.1.2-2_i386 +libplplot-ada0-dev_5.9.9-5_i386 +libplplot-dev_5.9.9-5_i386 +libplumb2-dev_1.0.9+hg2665-1_i386 +libplumbgpl2-dev_1.0.9+hg2665-1_i386 +libpmap3.0-dev_3.0.2+dfsg-4+b1_i386 +libpmi0-dev_2.3.4-2+b1_i386 +libpmount-dev_0.0.16_i386 +libpng++-dev_0.2.5-1_all +libpng12-dev_1.2.49-1_i386 +libpnglite-dev_0.1.17-1_i386 +libpoco-dev_1.3.6p1-4_i386 +libpodofo-dev_0.9.0-1.1+b1_i386 +libpoker-eval-dev_138.0-1_i386 +libpolarssl-dev_1.2.9-1~deb7u2_i386 +libpoldiff-dev_3.3.7-3_i386 +libpolkit-agent-1-dev_0.105-3_i386 +libpolkit-backend-1-dev_0.105-3_i386 +libpolkit-gobject-1-dev_0.105-3_i386 +libpolkit-qt-1-dev_0.103.0-1_i386 +libpolybori-dev_0.5~rc1-2.2_i386 +libpolylib64-dev_5.22.5-3+dfsg_i386 +libpolyml-dev_5.2.1-1.1_i386 +libpolyorb2-dev_2.8~20110207-5.1_i386 +libpomp-dev_1.1+dfsg-2_i386 +libpoppler-cil-dev_0.0.3-2_all +libpoppler-cpp-dev_0.18.4-6_i386 +libpoppler-dev_0.18.4-6_i386 +libpoppler-glib-dev_0.18.4-6_i386 +libpoppler-private-dev_0.18.4-6_i386 +libpoppler-qt4-dev_0.18.4-6_i386 +libpopplerkit-dev_0.0.20051227svn-7+b1_i386 +libpopt-dev_1.16-7_i386 +libportaudio-dev_18.1-7.1_i386 +libportaudio-ocaml-dev_0.2.0-1+b1_i386 +libportmidi-dev_1:184-2.1_i386 +libportsmf-dev_0.1~svn20101010-3_i386 +libpostgresql-ocaml-dev_1.18.0-1_i386 +libpostproc-dev_6:0.8.10-1_i386 +libpotrace-dev_1.10-1_i386 +libpowerman0-dev_2.3.5-1_i386 +libppd-dev_2:0.10-7.1_i386 +libppl0.11-dev_0.11.2-8_i386 +libpq-dev_9.1.13-0wheezy1_i386 +libpqxx3-dev_3.1-1.1_i386 +libprelude-dev_1.0.0-9_i386 +libpreludedb-dev_1.0.0-1.1+b2_i386 +libpresage-dev_0.8.8-1_i386 +libpri-dev_1.4.12-2_i386 +libprinterconf-dev_0.5-12_i386 +libprintsys-dev_0.6-13_i386 +libprison-dev_1.0+dfsg-1_i386 +libprocps0-dev_1:3.3.3-3_i386 +libproj-dev_4.7.0-2_i386 +libprojectm-dev_2.1.0+dfsg-1_i386 +libprojectm-qt-dev_2.1.0+dfsg-1_i386 +libprotobuf-c0-dev_0.14-1+b1_i386 +libprotobuf-dev_2.4.1-3_i386 +libprotoc-dev_2.4.1-3_i386 +libproxy-dev_0.3.1-6_i386 +libproxychains-dev_3.1-3_i386 +libpspell-dev_0.60.7~20110707-1_i386 +libpst-dev_0.6.54-4.1_i386 +libpstoedit-dev_3.60-2+b1_i386 +libpstreams-dev_0.7.0-2_all +libpt-dev_2.10.4~dfsg-1_i386 +libptexenc-dev_2012.20120628-4_i386 +libpth-dev_2.0.7-16_i386 +libpthread-stubs0-dev_0.3-3_i386 +libpthread-workqueue-dev_0.8.2-1_i386 +libptscotch-dev_5.1.12b.dfsg-1.2_i386 +libpugl-dev_0~svn32+dfsg0-1_i386 +libpulse-dev_2.0-6.1_i386 +libpulse-ocaml-dev_0.1.2-1+b1_i386 +libpuma-dev_1:1.1+svn20120529-2_i386 +libpurelibc-dev_0.4.1-1_i386 +libpurple-dev_2.10.9-1~deb7u1_all +libpuzzle-dev_0.9-5_i386 +libpwl-dev_0.11.2-8_i386 +libpxp-ocaml-dev_1.2.2-1+b4_i386 +libpycaml-ocaml-dev_0.82-14+b2_i386 +libpyside-dev_1.1.1-3_i386 +libpythia8-dev_8.1.65-1_i386 +libpythonqt2-dev_2.0.1-1.1_i386 +libqalculate-dev_0.9.7-8_i386 +libqapt-dev_1.3.0-2_i386 +libqb-dev_0.11.1-2_i386 +libqca2-dev_2.0.3-4_i386 +libqd-dev_2.3.11.dfsg-2.1_i386 +libqdaccolib-dev_0.8.2-1_i386 +libqdbm++-dev_1.8.78-2_i386 +libqdbm-dev_1.8.78-2_i386 +libqdjango-dev_0.2.5-2_i386 +libqedje-dev_0.4.0+lgpl-3_i386 +libqfits-dev_6.2.0-5_i386 +libqgis-dev_1.7.4+1.7.5~20120320-1.1+b1_i386 +libqglviewer-qt4-dev_2.3.4-4.2_i386 +libqgpsmm-dev_3.6-4+deb7u1_i386 +libqhull-dev_2009.1-3_i386 +libqimageblitz-dev_1:0.0.6-4_i386 +libqjson-dev_0.7.1-7_i386 +libqmf-dev_0.16-6+deb7u1_i386 +libqmf2-dev_0.16-6+deb7u1_i386 +libqmfconsole2-dev_0.16-6+deb7u1_i386 +libqmfengine1-dev_0.16-6+deb7u1_i386 +libqmmp-dev_0.5.5-1+b1_i386 +libqmmpui-dev_0.5.5-1+b1_i386 +libqoauth-dev_1.0.1-1_i386 +libqof-dev_0.8.6-1_i386 +libqofexpensesobjects-dev_0.1.9-2_i386 +libqpdf-dev_2.3.1-4_i386 +libqpidbroker2-dev_0.16-6+deb7u1_i386 +libqpidclient2-dev_0.16-6+deb7u1_i386 +libqpidcommon2-dev_0.16-6+deb7u1_i386 +libqpidmessaging2-dev_0.16-6+deb7u1_i386 +libqpidtypes1-dev_0.16-6+deb7u1_i386 +libqpol-dev_3.3.7-3_i386 +libqpx-dev_0.7.1.002-5_i386 +libqrencode-dev_3.3.0-2_i386 +libqrupdate-dev_1.1.1-1_i386 +libqsastime-dev_5.9.9-5_i386 +libqscintilla2-dev_2.6.2-2_all +libqt4-dev_4:4.8.2+dfsg-11_i386 +libqt4-opengl-dev_4:4.8.2+dfsg-11_i386 +libqt4-private-dev_4:4.8.2+dfsg-11_i386 +libqt4pas-dev_2.5-6_i386 +libqtassistantclient-dev_4.6.3-4_i386 +libqtexengine-dev_0.3-3_i386 +libqtgstreamer-dev_0.10.2-2_i386 +libqtruby4shared-dev_4:4.8.4-1_i386 +libqtwebkit-dev_2.2.1-5_i386 +libquantlib0-dev_1.2-2+b1_i386 +libquantum-dev_1.1.0-3_i386 +libquicktime-dev_2:1.2.4-3_i386 +libquorum-dev_1.4.2-3_i386 +libquvi-dev_0.4.1-1_i386 +libqwt-dev_6.0.0-1.2_i386 +libqwt5-qt4-dev_5.2.2-3_i386 +libqwtplot3d-qt4-dev_0.2.7+svn191-7_i386 +libqxmlrpc-dev_0.0.svn6-2_i386 +libqxmpp-dev_0.4.92-1_i386 +libqxt-dev_0.6.1-6_i386 +libqzeitgeist-dev_0.7.0-1+b1_i386 +libqzion-dev_0.4.0+lgpl-4_i386 +librabbitmq-dev_0.0.1.hg216-1_i386 +libradare2-dev_0.9-3_i386 +libradius1-dev_0.3.2-14_i386 +libradiusclient-ng-dev_0.5.6-1.1_i386 +libranlip-dev_1.0-4.1_i386 +librapi2-dev_0.15-2.1_i386 +libraptor1-dev_1.4.21-7.1_i386 +libraptor2-dev_2.0.8-2_i386 +librarian-dev_0.8.1-5_i386 +librasqal3-dev_0.9.29-1_i386 +librasterlite-dev_1.1~svn11-2_i386 +libraul-dev_0.8.0+dfsg0-0.1+b1_i386 +libraw-dev_0.14.6-2_i386 +libraw1394-dev_2.0.9-1_i386 +librcc-dev_0.2.9-3_i386 +librcd-dev_0.1.13-3_i386 +librdf0-dev_1.0.15-1+b1_i386 +librdkit-dev_201203-3_i386 +librdmacm-dev_1.0.15-1+deb7u1_i386 +librdmawrap2-dev_0.16-6+deb7u1_i386 +libreact-ocaml-dev_0.9.3-1_i386 +libreadline-dev_6.2+dfsg-0.1_i386 +libreadline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +libreadline6-dev_6.2+dfsg-0.1_i386 +librec-dev_1.5-1_i386 +librecode-dev_3.6-20_i386 +libref-array-dev_0.1.3-2_i386 +libregina3-dev_3.6-2_i386 +libregistry-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libreins-ocaml-dev_0.1a-4+b1_i386 +libreiser4-dev_1.0.7-6.3_i386 +librelp-dev_1.0.0-1_i386 +libremctl-dev_3.2-4_i386 +librenaissance0-dev_0.9.0-4+b3_i386 +libreoffice-dev_1:3.5.4+dfsg2-0+deb7u2_i386 +librep-dev_0.90.2-1.3_i386 +libreplaygain-dev_1.0~r475-1_i386 +libres-ocaml-dev_3.2.0-2+b3_i386 +libresample1-dev_0.1.3-4_i386 +libresid-builder-dev_2.1.1-14_i386 +libresiprocate-1.8-dev_1.8.5-4_i386 +libresiprocate-turn-client-1.8-dev_1.8.5-4_i386 +librest-dev_0.7.12-3_i386 +librest-extras-dev_0.7.12-3_i386 +librhash-cil-dev_1.2.9-8+deb7u1_all +librhash-dev_1.2.9-8+deb7u1_i386 +librheolef-dev_6.1-2.1_i386 +librivet-dev_1.8.0-1_i386 +librlog-dev_1.4-2_i386 +libroar-dev_1.0~beta2-3_i386 +libroot-bindings-python-dev_5.34.00-2_i386 +libroot-bindings-ruby-dev_5.34.00-2_i386 +libroot-core-dev_5.34.00-2_i386 +libroot-geom-dev_5.34.00-2_i386 +libroot-graf2d-gpad-dev_5.34.00-2_i386 +libroot-graf2d-graf-dev_5.34.00-2_i386 +libroot-graf2d-postscript-dev_5.34.00-2_i386 +libroot-graf3d-eve-dev_5.34.00-2_i386 +libroot-graf3d-g3d-dev_5.34.00-2_i386 +libroot-graf3d-gl-dev_5.34.00-2_i386 +libroot-gui-dev_5.34.00-2_i386 +libroot-gui-ged-dev_5.34.00-2_i386 +libroot-hist-dev_5.34.00-2_i386 +libroot-hist-spectrum-dev_5.34.00-2_i386 +libroot-html-dev_5.34.00-2_i386 +libroot-io-dev_5.34.00-2_i386 +libroot-io-xmlparser-dev_5.34.00-2_i386 +libroot-math-foam-dev_5.34.00-2_i386 +libroot-math-genvector-dev_5.34.00-2_i386 +libroot-math-mathcore-dev_5.34.00-2_i386 +libroot-math-mathmore-dev_5.34.00-2_i386 +libroot-math-matrix-dev_5.34.00-2_i386 +libroot-math-minuit-dev_5.34.00-2_i386 +libroot-math-mlp-dev_5.34.00-2_i386 +libroot-math-physics-dev_5.34.00-2_i386 +libroot-math-quadp-dev_5.34.00-2_i386 +libroot-math-smatrix-dev_5.34.00-2_i386 +libroot-math-splot-dev_5.34.00-2_i386 +libroot-math-unuran-dev_5.34.00-2_i386 +libroot-misc-memstat-dev_5.34.00-2_i386 +libroot-misc-minicern-dev_5.34.00-2_i386 +libroot-misc-table-dev_5.34.00-2_i386 +libroot-montecarlo-eg-dev_5.34.00-2_i386 +libroot-montecarlo-vmc-dev_5.34.00-2_i386 +libroot-net-auth-dev_5.34.00-2_i386 +libroot-net-bonjour-dev_5.34.00-2_i386 +libroot-net-dev_5.34.00-2_i386 +libroot-net-ldap-dev_5.34.00-2_i386 +libroot-proof-clarens-dev_5.34.00-2_i386 +libroot-proof-dev_5.34.00-2_i386 +libroot-proof-proofplayer-dev_5.34.00-2_i386 +libroot-roofit-dev_5.34.00-2_i386 +libroot-tmva-dev_5.34.00-2_i386 +libroot-tree-dev_5.34.00-2_i386 +libroot-tree-treeplayer-dev_5.34.00-2_i386 +librostlab-blast0-dev_1.0.0-2_i386 +librostlab3-dev_1.0.20-1_i386 +librpcsecgss-dev_0.19-5_i386 +librplay3-dev_3.3.2-14_i386 +librpm-dev_4.10.0-5+deb7u1_i386 +librra-dev_0.14-1.2_i386 +librrd-dev_1.4.7-2_i386 +librsl-dev_1.42-2_i386 +librsskit-dev_0.3-2_i386 +librsvg2-2.0-cil-dev_2.26.0-8_all +librsvg2-dev_2.36.1-2_i386 +librsync-dev_0.9.7-9_i386 +librtai-dev_3.8.1-4_i386 +librtas-dev_1.3.6-1_i386 +librtasevent-dev_1.3.6-1_i386 +librtaudio-dev_4.0.10~ds0-2_i386 +librtfcomp-dev_1.1-5+b1_i386 +librtfilter-dev_1.1-4_i386 +librtmidi-dev_1.0.15~ds0-2_i386 +librtmp-dev_2.4+20111222.git4e06e21-1_i386 +librubberband-dev_1.3-1.3_i386 +librudecgi-dev_5.0.0-1_i386 +libruli4-dev_0.33-1.1_i386 +librxp-dev_1.5.0-1_i386 +libs3-dev_2.0-1_i386 +libs3d-dev_0.2.2-8_i386 +libs3dw-dev_0.2.2-8_i386 +libsaamf3-dev_1.1.4-4.1_i386 +libsackpt3-dev_1.1.4-4.1_i386 +libsaclm3-dev_1.1.4-4.1_i386 +libsaevt3-dev_1.1.4-4.1_i386 +libsage-dev_0.2.0-4.1_i386 +libsalck3-dev_1.1.4-4.1_i386 +libsam-dev_1.4.2-3_i386 +libsamba-credentials-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-hostconfig-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-policy-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-util-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamdb-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsaml2-dev_2.4.3-4_i386 +libsampleicc-dev_1.6.4-1+b1_i386 +libsamplerate-ocaml-dev_0.1.1-1+b3_i386 +libsamplerate0-dev_0.1.8-5_i386 +libsamsg4-dev_1.1.4-4.1_i386 +libsane-dev_1.0.22-7.4_i386 +libsane-extras-dev_1.0.22.2_i386 +libsanlock-dev_2.2-2_i386 +libsary-dev_1:1.2.0-2.1_i386 +libsasl2-dev_2.1.25.dfsg1-6+deb7u1_i386 +libsatmr3-dev_1.1.4-4.1_i386 +libsbjson-dev_2.3.2-2_i386 +libsbsms-dev_2.0.1-1_i386 +libsbuf-dev_9.0+ds1-4_i386 +libsbuild-dev_1.6.4-4_i386 +libsc-dev_2.3.1-14_i386 +libscalapack-mpi-dev_1.8.0-9_i386 +libscalapack-pvm-dev_1.8.0-9_i386 +libscalc-dev_0.2.4-1_i386 +libscamperfile0-dev_20111202b-1_i386 +libschroedinger-dev_1.0.11-2_i386 +libschroedinger-ocaml-dev_0.1.0-1+b3_i386 +libscim-dev_1.4.13-5_i386 +libscm-dev_5e5-3.2_i386 +libscotch-dev_5.1.12b.dfsg-1.2_i386 +libscotchmetis-dev_5.1.12b.dfsg-1.2_i386 +libscotchparmetis-dev_5.1.12b.dfsg-1.2_i386 +libsctp-dev_1.0.11+dfsg-2_i386 +libscythestat-dev_1.0.2-1_all +libsdl-console-dev_2.1-3_i386 +libsdl-gfx1.2-dev_2.0.23-3_i386 +libsdl-image1.2-dev_1.2.12-2_i386 +libsdl-mixer1.2-dev_1.2.12-3_i386 +libsdl-net1.2-dev_1.2.8-2_i386 +libsdl-ocaml-dev_0.9.0-1_i386 +libsdl-pango-dev_0.1.2-6_i386 +libsdl-sge-dev_030809dfsg-3_i386 +libsdl-sound1.2-dev_1.0.3-6_i386 +libsdl-stretch-dev_0.3.1-3_i386 +libsdl-ttf2.0-dev_2.0.11-2_i386 +libsdl1.2-dev_1.2.15-5_i386 +libsdpa-dev_7.3.8+dfsg-1_i386 +libsearchclient-dev_0.7.7-3_i386 +libseaudit-dev_3.3.7-3_i386 +libseed-gtk3-dev_3.2.0-2_i386 +libsefs-dev_3.3.7-3_i386 +libselinux1-dev_2.1.9-5_i386 +libsemanage1-dev_2.1.6-6_i386 +libsensors-applet-plugin-dev_3.0.0-0.2_i386 +libsensors4-dev_1:3.3.2-2+deb7u1_i386 +libsepol1-dev_2.1.4-3_i386 +libserd-dev_0.14.0~dfsg0-2_i386 +libserf-dev_1.1.0-2_i386 +libsexplib-camlp4-dev_7.0.4-2_i386 +libsexy-dev_0.1.11-2+b1_i386 +libsfml-dev_1.6+dfsg2-2_i386 +libsfst1-1.2-0-dev_1.2.0-1.2_i386 +libsgutils2-dev_1.33-1_i386 +libsha-ocaml-dev_1.7-2+b2_i386 +libshairport-dev_1.2.1~git20120110.aeb4987-2_i386 +libshevek-dev_1.3-1_i386 +libshhmsg1-dev_1.4.1-4.1_i386 +libshhopt1-dev_1.1.7-2.1_i386 +libshiboken-dev_1.1.1-1_i386 +libshibsp-dev_2.4.3+dfsg-5+b1_i386 +libshisa-dev_1.0.1-2_i386 +libshishi-dev_1.0.1-2_i386 +libshout-ocaml-dev_0.2.7-1+b3_i386 +libshout3-dev_2.2.2-8_i386 +libshp-dev_1.2.10-7_i386 +libshr-glib-dev_2011.03.08.2~git20110930-2_i386 +libsidl-dev_1.4.0.dfsg-8.1_i386 +libsidplay1-dev_1.36.59-5_i386 +libsidplay2-dev_2.1.1-14_i386 +libsidplayfp-dev_0.3.5-1_i386 +libsidutils-dev_2.1.1-14_i386 +libsieve2-dev_2.2.6-1.1_i386 +libsigc++-1.2-dev_1.2.7-2_i386 +libsigc++-2.0-dev_2.2.10-0.2_i386 +libsigc++-dev_1.0.4-9.4_i386 +libsigrok0-dev_0.1.0-2_i386 +libsigrokdecode0-dev_0.1.0-2_i386 +libsigsegv-dev_2.9-4_i386 +libsilly-dev_0.1.0-3_i386 +libsilo-dev_4.8-13_i386 +libsimage-dev_1.7.0-1.1+b1_i386 +libsimplelist0-dev_0.3.4-2_i386 +libsipwitch-dev_1.2.4-1_i386 +libsiscone-dev_2.0.5-1_i386 +libsiscone-spherical-dev_2.0.5-1_i386 +libskk-dev_0.0.12-3_i386 +libskstream-0.3-dev_0.3.8-1_i386 +libslang2-dev_2.2.4-15_i386 +libslepc3.2-dev_3.2-p5-1_i386 +libslp-dev_1.2.1-9_i386 +libslurm-dev_2.3.4-2+b1_i386 +libslurmdb-dev_2.3.4-2+b1_i386 +libslv2-dev_0.6.6+dfsg1-2_i386 +libsm-dev_2:1.2.1-2_i386 +libsmbclient-dev_2:3.6.6-6+deb7u3_i386 +libsmbclient-raw-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsmbios-dev_2.0.3.dfsg-1.1_i386 +libsmf-dev_1.3-2_i386 +libsmi2-dev_0.4.8+dfsg2-7_i386 +libsmokekde-dev_4:4.8.4-1_i386 +libsmokeqt4-dev_4:4.8.4-1_i386 +libsmpeg-dev_0.4.5+cvs20030824-5_i386 +libsnacc-dev_1.3.1-1_i386 +libsnack2-dev_2.2.10-dfsg1-12.1_i386 +libsnappy-dev_1.0.5-2_i386 +libsndfile1-dev_1.0.25-5_i386 +libsndobj-dev_2.6.6.1-3_i386 +libsnmp-dev_5.4.3~dfsg-2.8_i386 +libsnmpkit-dev_0.9-16_i386 +libsocialweb-client-dev_0.25.20-2.1_i386 +libsocialweb-dev_0.25.20-2.1_i386 +libsocksd0-dev_1.1.19.dfsg-3+b3_i386 +libsofa-c-dev_2012.03.01-1_i386 +libsofa1-dev_1.0~beta4-7_i386 +libsofia-sip-ua-dev_1.12.11+20110422-1_i386 +libsofia-sip-ua-glib-dev_1.12.11+20110422-1_i386 +libsofthsm-dev_1.3.3-2_i386 +libsoil-dev_1.07~20080707.dfsg-2_i386 +libsombok-dev_2.2.1-1_i386 +libsonic-dev_0.1.17-1.1_i386 +libsope-dev_1.3.16-1_i386 +libsoprano-dev_2.7.6+dfsg.1-2wheezy1_i386 +libsoqt4-dev_1.5.0-2_i386 +libsord-dev_0.8.0~dfsg0-1_i386 +libsoundgen-dev_0.6-4_i386 +libsoundtouch-dev_1.6.0-3_i386 +libsoundtouch-ocaml-dev_0.1.7-1+b1_i386 +libsoup-gnome2.4-dev_2.38.1-3_i386 +libsoup2.4-dev_2.38.1-3_i386 +libsoupcutter-dev_1.1.7-1.2_i386 +libsource-highlight-dev_3.1.6-1.1_i386 +libsox-dev_14.4.0-3_i386 +libsp-gxmlcpp-dev_1.0.20040603-5_i386 +libsp1-dev_1.3.4-1.2.1-47.1+b1_i386 +libspandsp-dev_0.0.6~pre20-3.1_i386 +libsparsehash-dev_1.10-1_all +libsparskit-dev_2.0.0-2_i386 +libspatialindex-dev_1.7.0-1_i386 +libspatialite-dev_3.0.0~beta20110817-3+deb7u1_i386 +libspctag-dev_0.2-1_i386 +libspectre-dev_0.2.7-2_i386 +libspectrum-dev_1.0.0-3_i386 +libspeechd-dev_0.7.1-6.2_i386 +libspeex-dev_1.2~rc1-7_i386 +libspeex-ocaml-dev_0.2.0-1+b3_i386 +libspeexdsp-dev_1.2~rc1-7_i386 +libspf2-dev_1.2.9-7_i386 +libsphere-dev_3.2-4_i386 +libspice-client-glib-2.0-dev_0.12-5_i386 +libspice-client-gtk-2.0-dev_0.12-5_i386 +libspice-client-gtk-3.0-dev_0.12-5_i386 +libspice-protocol-dev_0.10.1-1_all +libspice-server-dev_0.11.0-1+deb7u1_i386 +libspiro-dev_20071029-2_i386 +libspnav-dev_0.2.2-1_i386 +libspooles-dev_2.2-9_i386 +libsprng2-dev_2.0a-8_i386 +libsqlexpr-ocaml-dev_0.4.1-1+b5_i386 +libsqlheavy-dev_0.1.1-1_i386 +libsqlheavygtk-dev_0.1.1-1_i386 +libsqlite0-dev_2.8.17-7_i386 +libsqlite3-dev_3.7.13-1+deb7u1_i386 +libsqlite3-ocaml-dev_1.6.1-1+b1_i386 +libsquizz-dev_0.99a-2_i386 +libsratom-dev_0.2.0~dfsg0-1_i386 +libsrecord-dev_1.58-1+b1_i386 +libsrf-dev_0.1+dfsg-1_i386 +libsrtp0-dev_1.4.4+20100615~dfsg-2+deb7u1_i386 +libss7-dev_1.0.2-3_i386 +libsscm-dev_0.8.5-2.1_i386 +libssh-dev_0.5.4-1+deb7u1_i386 +libssh2-1-dev_1.4.2-1.1_i386 +libssl-dev_1.0.1e-2+deb7u7_i386 +libssl-ocaml-dev_0.4.6-1_i386 +libsslcommon2-dev_0.16-6+deb7u1_i386 +libssreflect-ocaml-dev_1.3pl4-1_i386 +libsss-sudo-dev_1.8.4-2_i386 +libst-dev_1.9-3_i386 +libstaden-read-dev_1.12.4-1_i386 +libstarlink-ast-dev_7.0.4+dfsg-1_i386 +libstarlink-pal-dev_0.1.0-1_i386 +libstarpu-dev_1.0.1+dfsg-1_i386 +libstartup-notification0-dev_0.12-1_i386 +libstatgrab-dev_0.17-1_i386 +libstdc++6-4.4-dev_4.4.7-2_i386 +libstdc++6-4.6-dev_4.6.3-14_i386 +libstdc++6-4.7-dev_4.7.2-5_i386 +libstemmer-dev_0+svn546-2_i386 +libsteptalk-dev_0.10.0-5+b1_i386 +libstfl-dev_0.22-1+b1_i386 +libstk0-dev_4.4.3-2_i386 +libstlport4.6-dev_4.6.2-7_i386 +libstonith1-dev_1.0.9+hg2665-1_i386 +libstonithd1-dev_1.1.7-1_i386 +libstreamanalyzer-dev_0.7.7-3_i386 +libstreams-dev_0.7.7-3_i386 +libstrigihtmlgui-dev_0.7.7-3_i386 +libstrigiqtdbusclient-dev_0.7.7-3_i386 +libstroke0-dev_0.5.1-6_i386 +libstxxl-dev_1.3.1-4_i386 +libsublime-dev_1.3.1-2_i386 +libsubtitleeditor-dev_0.33.0-1_i386 +libsubunit-dev_0.0.8+bzr176-1_i386 +libsugarext-dev_0.96.1-2_i386 +libsuil-dev_0.6.4~dfsg0-3_i386 +libsuitesparse-dev_1:3.4.0-3_i386 +libsundials-serial-dev_2.5.0-3_i386 +libsunpinyin-dev_2.0.3+git20120607-1_i386 +libsuperlu3-dev_3.0+20070106-3_i386 +libsvga1-dev_1:1.4.3-33_i386 +libsvm-dev_3.12-1_i386 +libsvn-dev_1.6.17dfsg-4+deb7u6_i386 +libsvncpp-dev_0.12.0dfsg-6_i386 +libsvnqt-dev_1.5.5-4.1_i386 +libsvrcore-dev_1:4.0.4-15_i386 +libswami-dev_2.0.0+svn389-2_i386 +libswe-dev_1.77.00.0005-2_i386 +libswiften-dev_2.0~beta1+dev47-1_i386 +libsword-dev_1.6.2+dfsg-5_i386 +libswscale-dev_6:0.8.10-1_i386 +libsx-dev_2.05-3_i386 +libsyfi1.0-dev_1.0.0.dfsg-1_i386 +libsylph-dev_1.1.0-8_i386 +libsymmetrica-dev_2.0-1_i386 +libsynce0-dev_0.15-1.1_i386 +libsyncml-dev_0.5.4-2.1_i386 +libsynfig-dev_0.63.05-1_i386 +libsynopsis0.12-dev_0.12-8_i386 +libsynthesis-dev_3.4.0.16.7-1_i386 +libsysactivity-dev_0.6.4-1_i386 +libsysfs-dev_2.1.0+repack-2_i386 +libsyslog-ng-dev_3.3.5-4_i386 +libsyslog-ocaml-dev_1.4-6+b2_i386 +libsystemd-daemon-dev_44-11+deb7u4_i386 +libsystemd-id128-dev_44-11+deb7u4_i386 +libsystemd-journal-dev_44-11+deb7u4_i386 +libsystemd-login-dev_44-11+deb7u4_i386 +libt1-dev_5.1.2-3.6_i386 +libtacacs+1-dev_4.0.4.19-11_all +libtachyon-dev_0.99~b2+dfsg-0.4_i386 +libtag-extras-dev_1.0.1-3_i386 +libtag1-dev_1.7.2-1_i386 +libtagc0-dev_1.7.2-1_i386 +libtagcoll2-dev_2.0.13-1.1_i386 +libtaglib-cil-dev_2.0.4.0-1_all +libtaglib-ocaml-dev_0.2.0-1+b1_i386 +libtaktuk-1-dev_3.7.4-1_i386 +libtalloc-dev_2.0.7+git20120207-1_i386 +libtamuanova-dev_0.2-2_i386 +libtango7-dev_7.2.6+dfsg-14_i386 +libtaningia-dev_0.2.2-1_i386 +libtaoframework-devil-cil-dev_2.1.svn20090801-9_all +libtaoframework-ffmpeg-cil-dev_2.1.svn20090801-9_all +libtaoframework-freeglut-cil-dev_2.1.svn20090801-9_all +libtaoframework-freetype-cil-dev_2.1.svn20090801-9_all +libtaoframework-ftgl-cil-dev_2.1.svn20090801-9_all +libtaoframework-lua-cil-dev_2.1.svn20090801-9_all +libtaoframework-ode-cil-dev_2.1.svn20090801-9_all +libtaoframework-openal-cil-dev_2.1.svn20090801-9_all +libtaoframework-opengl-cil-dev_2.1.svn20090801-9_all +libtaoframework-physfs-cil-dev_2.1.svn20090801-9_all +libtaoframework-sdl-cil-dev_2.1.svn20090801-9_all +libtar-dev_1.2.16-1+deb7u2_i386 +libtarantool-dev_1.4.6+20120629+2158-1_i386 +libtasn1-3-dev_2.13-2_i386 +libtbb-dev_4.0+r233-1_i386 +libtcc-dev_0.9.26~git20120612.ad5f375-6_i386 +libtcd-dev_2.2.2-1_i386 +libtclap-dev_1.2.1-1_i386 +libtclcl1-dev_1.20-6_i386 +libtdb-dev_1.2.10-2_i386 +libtecla1-dev_1.6.1-5_i386 +libteem-dev_1.11.0~svn5226-1_i386 +libtelepathy-farstream-dev_0.4.0-3_i386 +libtelepathy-glib-dev_0.18.2-2_i386 +libtelepathy-logger-dev_0.4.0-1_i386 +libtelepathy-logger-qt4-dev_0.4.0-1_i386 +libtelepathy-qt4-dev_0.9.1-4_i386 +libtelnet-dev_0.21-1_i386 +libtemplates-parser11.6-dev_11.6-2_i386 +libterralib-dev_4.0.0-4_i386 +libtesseract-dev_3.02.01-6_i386 +libtevent-dev_0.9.16-1_i386 +libtext-ocaml-dev_0.5-1+b2_i386 +libtextwrap-dev_0.1-13_i386 +libthai-dev_0.1.18-2_i386 +libtheora-dev_1.1.1+dfsg.1-3.1_i386 +libtheora-ocaml-dev_0.3.0-1+b3_i386 +libthepeg-dev_1.8.0-1_i386 +libthrust-dev_1.6.0-1_all +libthunar-vfs-1-dev_1.2.0-3+b1_i386 +libthunarx-2-dev_1.2.3-4+b1_i386 +libticables-dev_1.2.0-2_i386 +libticalcs-dev_1.1.3+dfsg1-1_i386 +libticonv-dev_1.1.0-1.1_i386 +libtidy-dev_20091223cvs-1.2_i386 +libtiff4-dev_3.9.6-11_i386 +libtiff5-alt-dev_4.0.2-6+deb7u2_i386 +libtiff5-dev_4.0.2-6+deb7u2_i386 +libtifiles-dev_1.1.1-1_i386 +libtimbl3-dev_6.4.2-1_i386 +libtimblserver2-dev_1.4-2_i386 +libtinfo-dev_5.9-10_i386 +libtinyxml-dev_2.6.2-1_i386 +libtinyxml2-dev_0~git20120518.1.a2ae54e-1_i386 +libtirpc-dev_0.2.2-5_i386 +libtk-img-dev_1:1.3-release-12_i386 +libtnt-dev_1.2.6-1_all +libtntdb-dev_1.2-2+b1_i386 +libtntnet-dev_2.1-2+deb7u1_i386 +libtododb-dev_0.11-3_i386 +libtogl-dev_1.7-12_all +libtokyocabinet-dev_1.4.47-2_i386 +libtokyotyrant-dev_1.1.40-4.1+b1_i386 +libtolua++5.1-dev_1.0.93-3_i386 +libtolua-dev_5.2.0-1_i386 +libtomcrypt-dev_1.17-3.2_i386 +libtommath-dev_0.42.0-1_i386 +libtomoe-dev_0.6.0-1.3_i386 +libtonezone-dev_1:2.5.0.1-2_i386 +libtophide-ocaml-dev_1.0.0-3_all +libtorch3-dev_3.1-2.1_i386 +libtorque2-dev_2.4.16+dfsg-1+deb7u2_i386 +libtorrent-dev_0.13.2-1_i386 +libtorrent-rasterbar-dev_0.15.10-1+b1_i386 +libtorture-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libtotem-dev_3.0.1-8_i386 +libtotem-pg-dev_1.4.2-3_i386 +libtotem-plparser-dev_3.4.2-1_i386 +libtowitoko-dev_2.0.7-8.3_i386 +libtpl-dev_1.5-2_all +libtpm-unseal-dev_1.3.7-1_i386 +libtrace3-dev_3.0.14-1_i386 +libtracker-extract-0.14-dev_0.14.1-3_i386 +libtracker-miner-0.14-dev_0.14.1-3_i386 +libtracker-sparql-0.14-dev_0.14.1-3_i386 +libtransitioner1-dev_1.1.7-1_i386 +libtre-dev_0.8.0-3_i386 +libtreil-dev_1.8-1.1_i386 +libts-dev_1.0-11_i386 +libtse3-dev_0.3.1-4.3_i386 +libtsk-dev_3.2.3-2_i386 +libtspi-dev_0.3.9-3+wheezy1_i386 +libtulip-dev_3.7.0dfsg-4_i386 +libtumbler-1-dev_0.1.25-1+b1_i386 +libtut-dev_0.0.20070706-1_all +libtuxcap-dev_1.4.0.dfsg2-2.1_i386 +libtwin-dev_12.04.13.17.57-g130ee5f-2_i386 +libtwofish-dev_0.3-3_i386 +libtwolame-dev_0.3.13-1_i386 +libtxc-dxtn-s2tc-dev_0~git20110809-3_i386 +libtype-conv-camlp4-dev_3.0.4-1_i386 +libtyxml-ocaml-dev_2.1-1_i386 +libuchardet-dev_0.0.1-1_i386 +libucimf-dev_2.3.8-4_i386 +libucl-dev_1.03-5_i386 +libuclmmbase1-dev_1.2.16.0-1_i386 +libucommon-dev_5.2.2-4_i386 +libucto1-dev_0.5.2-2_i386 +libudev-dev_175-7.2_i386 +libudf-dev_0.83-4_i386 +libudt-dev_4.10+dfsg-1_i386 +libudunits2-dev_2.1.23-3_i386 +libuhd-dev_3.4.2-1_i386 +libuim-dev_1:1.8.1-4_i386 +libumlib-dev_0.8.2-1_i386 +libunac1-dev_1.8.0-6_i386 +libunbound-dev_1.4.17-3_i386 +libunicap2-dev_0.9.12-2_i386 +libuninameslist-dev_0.0.20091231-1.1_i386 +libuninum-dev_2.7-1.1_i386 +libunique-3.0-dev_3.0.2-1_i386 +libunique-dev_1.1.6-4_i386 +libunistring-dev_0.9.3-5_i386 +libunittest++-dev_1.4.0-3_i386 +libunshield-dev_0.6-3_i386 +libunwind-setjmp0-dev_0.99-0.3_i386 +libunwind7-dev_0.99-0.3_i386 +libupnp-dev_1:1.6.17-1.2_all +libupnp4-dev_1.8.0~svn20100507-1.2_i386 +libupnp6-dev_1:1.6.17-1.2_i386 +libupower-glib-dev_0.9.17-1_i386 +libupsclient1-dev_2.6.4-2.3+deb7u1_i386 +libupse-dev_1.0.0-1_i386 +libuptimed-dev_1:0.3.17-3.1_i386 +liburcu-dev_0.6.7-2_i386 +liburfkill-glib-dev_0.3.0-1_i386 +liburg0-dev_0.8.12-4_i386 +liburiparser-dev_0.7.5-1_i386 +libusb++-dev_2:0.1.12-20+nmu1_i386 +libusb-1.0-0-dev_2:1.0.11-1_i386 +libusb-dev_2:0.1.12-20+nmu1_i386 +libusb-ocaml-dev_1.2.0-2+b7_i386 +libusbip-dev_1.1.1+3.2.17-1_i386 +libusbmuxd-dev_1.0.7-2_i386 +libusbprog-dev_0.2.0-2_i386 +libusbredirhost-dev_0.4.3-2_i386 +libusbredirparser-dev_0.4.3-2_i386 +libusbtc08-dev_1.7.2-1_i386 +libuser1-dev_1:0.56.9.dfsg.1-1.2_i386 +libust-dev_2.0.4-1_i386 +libustr-dev_1.0.4-3_i386 +libutempter-dev_1.1.5-4_i386 +libuu-dev_0.5.20-3.3_i386 +libuuidm-ocaml-dev_0.9.4-1_i386 +libv4l-dev_0.8.8-3_i386 +libv8-dev_3.8.9.20-2_i386 +libv8-i18n-dev_0~0.svn7-3_i386 +libva-dev_1.0.15-4_i386 +libvala-0.14-dev_0.14.2-2_i386 +libvala-0.16-dev_0.16.1-2_i386 +libvaladoc-dev_0.3.2~git20120227-1_i386 +libvalhalla-dev_2.0.0-4+b1_i386 +libvanessa-adt-dev_0.0.9-1_i386 +libvanessa-logger-dev_0.0.10-1.1_i386 +libvanessa-socket-dev_0.0.12-1_i386 +libvarconf-dev_0.6.7-2_i386 +libvarnishapi-dev_3.0.2-2+deb7u1_i386 +libvbr-dev_2.6.8-4_i386 +libvc-dev_003.dfsg.1-12_i386 +libvcdinfo-dev_0.7.24+dfsg-0.1_i386 +libvde-dev_2.3.2-4_i386 +libvdeplug-dev_2.3.2-4_i386 +libvdk2-dev_2.4.0-5.3_i386 +libvdkbuilder2-dev_2.4.0-4.3_i386 +libvdkxdb2-dev_2.4.0-3.4_i386 +libvdpau-dev_0.4.1-7_i386 +libventrilo-dev_1.2.4-1_i386 +libverbiste-dev_0.1.34-1_i386 +libverto-dev_0.2.2-1_i386 +libvformat-dev_1.13-10_i386 +libvia-dev_2.0.4-2_i386 +libvibrant6-dev_6.1.20120620-2_i386 +libview-dev_0.6.6-2.1_i386 +libvigraimpex-dev_1.7.1+dfsg1-3_i386 +libvips-dev_7.28.5-1+deb7u1_i386 +libvirt-dev_0.9.12.3-1_i386 +libvirt-glib-1.0-dev_0.0.8-1_i386 +libvirt-ocaml-dev_0.6.1.2-1_i386 +libvisca-dev_1.0.1-1_i386 +libvisio-dev_0.0.17-1_i386 +libvisual-0.4-dev_0.4.0-5_i386 +libvlc-dev_2.0.3-5_i386 +libvlccore-dev_2.0.3-5_i386 +libvmmlib-dev_1.0-2_all +libvncserver-dev_0.9.9+dfsg-1_i386 +libvo-aacenc-dev_0.1.2-1_i386 +libvo-amrwbenc-dev_0.1.2-1_i386 +libvoaacenc-ocaml-dev_0.1.0-1+b1_i386 +libvoikko-dev_3.5-1.1_i386 +libvolpack1-dev_1.0b3-3_i386 +libvorbis-dev_1.3.2-1.3_i386 +libvorbis-ocaml-dev_0.6.1-1+b1_i386 +libvorbisidec-dev_1.0.2+svn18153-0.2_i386 +libvotequorum-dev_1.4.2-3_i386 +libvpb-dev_4.2.55-1_i386 +libvpx-dev_1.1.0-1_i386 +libvrb0-dev_0.5.1-5.1_i386 +libvte-2.90-dev_1:0.32.2-1_i386 +libvte-dev_1:0.28.2-5_i386 +libvte0.16-cil-dev_2.26.0-8_i386 +libvtk5-dev_5.8.0-13+b1_i386 +libvtk5-qt4-dev_5.8.0-13+b1_i386 +libvtkedge-dev_0.2.0~20110819-2_i386 +libvtkgdcm2-dev_2.2.0-14.1_i386 +libvxl1-dev_1.14.0-18_i386 +libwacom-dev_0.6-1_i386 +libwaei-dev_3.4.3-1_i386 +libwaili-dev_19990723-20_i386 +libwavefront-standalone3.0-dev_3.0.2+dfsg-4+b1_i386 +libwavpack-dev_4.60.1-3_i386 +libwayland-dev_0.85.0-2_i386 +libwbclient-dev_2:3.6.6-6+deb7u3_i386 +libwbxml2-dev_0.10.7-1_i386 +libwcstools-dev_3.8.5-1_i386 +libwebauth-dev_4.1.1-2_i386 +libwebcam0-dev_0.2.2-1_i386 +libwebkit-cil-dev_0.3-6_all +libwebkit-dev_1.8.1-3.4_i386 +libwebkitgtk-3.0-dev_1.8.1-3.4_i386 +libwebkitgtk-dev_1.8.1-3.4_i386 +libwebp-dev_0.1.3-3+nmu1_i386 +libwebrtc-audio-processing-dev_0.1-2_i386 +libweed-dev_1.6.2~ds1-2_i386 +libwfmath-0.3-dev_0.3.12-3_i386 +libwfut-0.2-dev_0.2.1-2_i386 +libwibble-dev_0.1.28-1.1_i386 +libwildmidi-dev_0.2.3.4-2.1_i386 +libwine-dev_1.4.1-4_i386 +libwings-dev_0.95.3-2_i386 +libwireshark-dev_1.8.2-5wheezy10_i386 +libwiretap-dev_1.8.2-5wheezy10_i386 +libwmf-dev_0.2.8.4-10.3_i386 +libwnck-3-dev_3.4.2-1_i386 +libwnck-dev_2.30.7-1_i386 +libwnck1.0-cil-dev_2.26.0-8_i386 +libwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libwnn6-dev_1.0.0-14.2+b1_i386 +libwpd-dev_0.9.4-3_i386 +libwpg-dev_0.2.1-1_i386 +libwps-dev_0.2.7-1_i386 +libwrap0-dev_7.6.q-24_i386 +libwraster3-dev_0.95.3-2_i386 +libwreport-dev_2.4-1_i386 +libwsutil-dev_1.8.2-5wheezy10_i386 +libwt-dev_3.2.1-2_i386 +libwtdbo-dev_3.2.1-2_i386 +libwtdbofirebird-dev_3.2.1-2_i386 +libwtdbopostgres-dev_3.2.1-2_i386 +libwtdbosqlite-dev_3.2.1-2_i386 +libwtext-dev_3.2.1-2_i386 +libwtfcgi-dev_3.2.1-2_i386 +libwthttp-dev_3.2.1-2_i386 +libwttest-dev_3.2.1-2_i386 +libwv-dev_1.2.9-3_i386 +libwv2-dev_0.4.2.dfsg.2-1~deb7u1_i386 +libwvstreams-dev_4.6.1-5_i386 +libwxbase2.8-dev_2.8.12.1-12_i386 +libwxgtk2.8-dev_2.8.12.1-12_i386 +libwxsmithlib-dev_10.05-2.1_i386 +libwxsmithlib0-dev_10.05-2.1_i386 +libwxsqlite3-2.8-dev_3.0.0.1~dfsg0-2_i386 +libwxsvg-dev_2:1.1.8~dfsg0-2_i386 +libx11-dev_2:1.5.0-1+deb7u1_i386 +libx11-xcb-dev_2:1.5.0-1+deb7u1_i386 +libx264-dev_2:0.123.2189+git35cf912-1_i386 +libx52pro-dev_0.1.1-2.1_i386 +libx86-dev_1.1+ds1-10_i386 +libxalan110-dev_1.10-6_i386 +libxapian-dev_1.2.12-2_i386 +libxatracker-dev_8.0.5-4+deb7u2_i386 +libxau-dev_1:1.0.7-1_i386 +libxaw7-dev_2:1.0.10-2_i386 +libxbae-dev_4.60.4-3_i386 +libxbase2.0-dev_2.0.0-8.5_i386 +libxcb-composite0-dev_1.8.1-2+deb7u1_i386 +libxcb-damage0-dev_1.8.1-2+deb7u1_i386 +libxcb-dpms0-dev_1.8.1-2+deb7u1_i386 +libxcb-dri2-0-dev_1.8.1-2+deb7u1_i386 +libxcb-ewmh-dev_0.3.9-2_i386 +libxcb-glx0-dev_1.8.1-2+deb7u1_i386 +libxcb-icccm4-dev_0.3.9-2_i386 +libxcb-image0-dev_0.3.9-1_i386 +libxcb-keysyms1-dev_0.3.9-1_i386 +libxcb-randr0-dev_1.8.1-2+deb7u1_i386 +libxcb-record0-dev_1.8.1-2+deb7u1_i386 +libxcb-render-util0-dev_0.3.8-1.1_i386 +libxcb-render0-dev_1.8.1-2+deb7u1_i386 +libxcb-res0-dev_1.8.1-2+deb7u1_i386 +libxcb-screensaver0-dev_1.8.1-2+deb7u1_i386 +libxcb-shape0-dev_1.8.1-2+deb7u1_i386 +libxcb-shm0-dev_1.8.1-2+deb7u1_i386 +libxcb-sync0-dev_1.8.1-2+deb7u1_i386 +libxcb-util0-dev_0.3.8-2_i386 +libxcb-xevie0-dev_1.8.1-2+deb7u1_i386 +libxcb-xf86dri0-dev_1.8.1-2+deb7u1_i386 +libxcb-xfixes0-dev_1.8.1-2+deb7u1_i386 +libxcb-xinerama0-dev_1.8.1-2+deb7u1_i386 +libxcb-xprint0-dev_1.8.1-2+deb7u1_i386 +libxcb-xtest0-dev_1.8.1-2+deb7u1_i386 +libxcb-xv0-dev_1.8.1-2+deb7u1_i386 +libxcb-xvmc0-dev_1.8.1-2+deb7u1_i386 +libxcb1-dev_1.8.1-2+deb7u1_i386 +libxcomp-dev_3.5.0.12-1+b1_i386 +libxcomposite-dev_1:0.4.3-2_i386 +libxcp-ocaml-dev_0.5.2-3+b1_i386 +libxcrypt-dev_1:2.4-3_i386 +libxcursor-dev_1:1.1.13-1+deb7u1_i386 +libxdamage-dev_1:1.1.3-2_i386 +libxdb-dev_1.2.0-7.2_i386 +libxdelta2-dev_1.1.3-9_i386 +libxdffileio-dev_0.3-1_i386 +libxdg-basedir-dev_1.1.1-2_i386 +libxdmcp-dev_1:1.1.1-1_i386 +libxdmf-dev_2.1.dfsg.1-5_i386 +libxdo-dev_1:2.20100701.2961-3+deb7u3_i386 +libxen-dev_4.1.4-3+deb7u1_i386 +libxen-ocaml-dev_4.1.4-3+deb7u1_i386 +libxenapi-ocaml-dev_1.3.2-15_i386 +libxenomai-dev_2.6.0-2_i386 +libxerces-c-dev_3.1.1-3_i386 +libxerces-c2-dev_2.8.0+deb1-3_i386 +libxext-dev_2:1.3.1-2+deb7u1_i386 +libxfce4menu-0.1-dev_4.6.2-1_i386 +libxfce4ui-1-dev_4.8.1-1_i386 +libxfce4util-dev_4.8.2-1_i386 +libxfcegui4-dev_4.8.1-5_i386 +libxfconf-0-dev_4.8.1-1_i386 +libxfixes-dev_1:5.0-4+deb7u1_i386 +libxfont-dev_1:1.4.5-3_i386 +libxft-dev_2.3.1-1_i386 +libxi-dev_2:1.6.1-1+deb7u1_i386 +libxine-dev_1.1.21-1+deb7u1_i386 +libxine2-dev_1.2.2-5_i386 +libxinerama-dev_2:1.1.2-1+deb7u1_i386 +libxkbfile-dev_1:1.0.8-1_i386 +libxklavier-dev_5.2.1-1_i386 +libxml++2.6-dev_2.34.2-1_i386 +libxml-light-ocaml-dev_2.2-15_i386 +libxml-security-c-dev_1.6.1-5+deb7u2_i386 +libxml2-dev_2.8.0+dfsg1-7+nmu3_i386 +libxmlada4.1-dev_4.1-2_i386 +libxmlezout2-dev_1.06.1-5_i386 +libxmlm-ocaml-dev_1.1.0-1_i386 +libxmlplaylist-ocaml-dev_0.1.3-1+b2_i386 +libxmlrpc-c++4-dev_1.16.33-3.2_i386 +libxmlrpc-c3-dev_1.16.33-3.2_all +libxmlrpc-core-c3-dev_1.16.33-3.2_i386 +libxmlrpc-epi-dev_0.54.2-1_i386 +libxmlrpc-light-ocaml-dev_0.6.1-3+b5_i386 +libxmlsec1-dev_1.2.18-2_i386 +libxmltok1-dev_1.2-3_i386 +libxmltooling-dev_1.4.2-5_i386 +libxmmsclient++-dev_0.8+dfsg-4_i386 +libxmmsclient++-glib-dev_0.8+dfsg-4_i386 +libxmmsclient-dev_0.8+dfsg-4_i386 +libxmmsclient-glib-dev_0.8+dfsg-4_i386 +libxmpi4-dev_2.2.3b8-13_i386 +libxmu-dev_2:1.1.1-1_i386 +libxmuu-dev_2:1.1.1-1_i386 +libxnee-dev_3.13-1_i386 +libxneur-dev_0.15.0-1.1_i386 +libxosd-dev_2.2.14-2_i386 +libxp-dev_1:1.0.1-2+deb7u1_i386 +libxpa-dev_2.1.14-2_i386 +libxplc0.3.13-dev_0.3.13-3_i386 +libxpm-dev_1:3.5.10-1_i386 +libxqdbm-dev_1.8.78-2_i386 +libxqilla-dev_2.3.0-1_i386 +libxr1-dev_1.0-2.1_i386 +libxrandr-dev_2:1.3.2-2+deb7u1_i386 +libxrender-dev_1:0.9.7-1+deb7u1_i386 +libxres-dev_2:1.0.6-1+deb7u1_i386 +libxsettings-client-dev_0.17-6_i386 +libxsettings-dev_0.11-3_i386 +libxslt1-dev_1.1.26-14.1_i386 +libxss-dev_1:1.2.2-1_i386 +libxstr-ocaml-dev_0.2.1-21+b3_i386 +libxstrp4-camlp4-dev_1.8-3_all +libxt-dev_1:1.1.3-1+deb7u1_i386 +libxtst-dev_2:1.2.1-1+deb7u1_i386 +libxv-dev_2:1.0.7-1+deb7u1_i386 +libxvidcore-dev_2:1.3.2-9_i386 +libxvmc-dev_2:1.0.7-1+deb7u2_i386 +libxxf86dga-dev_2:1.1.3-2+deb7u1_i386 +libxxf86vm-dev_1:1.1.2-1+deb7u1_i386 +libxy-dev_0.8-1+b1_i386 +libyahoo2-dev_1.0.1-1_i386 +libyajl-dev_2.0.4-2_i386 +libyaml-cpp-dev_0.3.0-1_i386 +libyaml-dev_0.1.4-2+deb7u4_i386 +libyaz4-dev_4.2.30-2_i386 +libyelp-dev_3.4.2-1+b1_i386 +libygl4-dev_4.2e-4_i386 +libykclient-dev_2.6-1_i386 +libykpers-1-dev_1.7.0-1_i386 +libyojson-ocaml-dev_1.0.3-1_i386 +libytnef0-dev_1.5-4_i386 +libyubikey-dev_1.8-1_i386 +libz80ex-dev_1.1.19-3_i386 +libzarith-ocaml-dev_1.1-2_i386 +libzbar-dev_0.10+doc-8_i386 +libzbargtk-dev_0.10+doc-8_i386 +libzbarqt-dev_0.10+doc-8_i386 +libzeep-dev_2.9.0-2_i386 +libzeitgeist-cil-dev_0.8.0.0-4_all +libzeitgeist-dev_0.3.18-1_i386 +libzen-dev_0.4.27-2_i386 +libzephyr-dev_3.0.2-2_i386 +libzerg0-dev_1.0.7-3_i386 +libzeroc-ice34-dev_3.4.2-8.2_i386 +libzinnia-dev_0.06-1+b1_i386 +libzip-dev_0.10.1-1.1_i386 +libzip-ocaml-dev_1.04-6+b3_i386 +libzipios++-dev_0.1.5.9+cvs.2007.04.28-5.1_i386 +libzita-alsa-pcmi-dev_0.2.0-1_i386 +libzita-convolver-dev_3.1.0-2_i386 +libzita-resampler-dev_1.1.0-3_i386 +libzlcore-dev_0.12.10dfsg-8_i386 +libzltext-dev_0.12.10dfsg-8_i386 +libzmq-dev_2.2.0+dfsg-2_i386 +libzn-poly-dev_0.8-1.1_i386 +libzookeeper-mt-dev_3.3.5+dfsg1-2_i386 +libzookeeper-st-dev_3.3.5+dfsg1-2_i386 +libzorp-dev_3.9.5-4_i386 +libzorpll-dev_3.9.1.3-1_i386 +libzrtpcpp-dev_2.0.0-3_i386 +libzthread-dev_2.3.2-7_i386 +libzvbi-dev_0.2.33-6_i386 +libzzip-dev_0.13.56-1.1_i386 +licq-dev_1.6.1-3_all +linux-libc-dev_3.2.57-3_i386 +lldpad-dev_0.9.44-1_i386 +llvm-2.9-dev_2.9+dfsg-7_i386 +llvm-3.0-dev_3.0-10_i386 +llvm-3.1-dev_3.1-1_i386 +llvm-dev_1:3.0-14+nmu2_i386 +lttv-dev_0.12.38-21032011-1+b1_i386 +lua-apr-dev_0.23.2-1_i386 +lua-bitop-dev_1.0.2-1_i386 +lua-curl-dev_0.3.0-7_i386 +lua-curses-dev_5.1.19-2_i386 +lua-cyrussasl-dev_1.0.0-4_i386 +lua-dbi-mysql-dev_0.5+svn78-4_i386 +lua-dbi-postgresql-dev_0.5+svn78-4_i386 +lua-dbi-sqlite3-dev_0.5+svn78-4_i386 +lua-event-dev_0.4.1-2_i386 +lua-expat-dev_1.2.0-5+deb7u1_i386 +lua-filesystem-dev_1.5.0+16+g84f1af5-1_i386 +lua-iconv-dev_7-1_i386 +lua-ldap-dev_1.1.0-1-geeac494-3_i386 +lua-leg-dev_0.1.2-8_all +lua-lgi-dev_0.6.2-1_i386 +lua-lpeg-dev_0.10.2-5_i386 +lua-md5-dev_1.1.2-6_i386 +lua-penlight-dev_1.0.2+htmldoc-2_all +lua-posix-dev_5.1.19-2_i386 +lua-rex-onig-dev_2.6.0-2_i386 +lua-rex-pcre-dev_2.6.0-2_i386 +lua-rex-posix-dev_2.6.0-2_i386 +lua-rex-tre-dev_2.6.0-2_i386 +lua-rings-dev_1.2.3-1_i386 +lua-sec-dev_0.4.1-1_i386 +lua-socket-dev_2.0.2-8_i386 +lua-sql-mysql-dev_2.3.0-1+build0_i386 +lua-sql-postgres-dev_2.3.0-1+build0_i386 +lua-sql-sqlite3-dev_2.3.0-1+build0_i386 +lua-svn-dev_0.4.0-7_i386 +lua-wsapi-fcgi-dev_1.5-3_i386 +lua-zip-dev_1.2.3-11_i386 +lua-zlib-dev_0.2-1_i386 +lua5.1-policy-dev_33_all +lv2-dev_1.0.0~dfsg2-2_i386 +lxc-dev_0.8.0~rc1-8+deb7u2_i386 +lzma-dev_9.22-2_all +manpages-de-dev_1.2-1_all +manpages-dev_3.44-1_all +manpages-fr-dev_3.44d1p1-1_all +manpages-ja-dev_0.5.0.0.20120606-1_all +manpages-pl-dev_1:0.3-1_all +manpages-pt-dev_20040726-4_all +matlab-support-dev_0.0.18_all +mdbtools-dev_0.7-1+deb7u1_i386 +med-bio-dev_1.13.2_all +med-imaging-dev_1.13.2_all +mesa-common-dev_8.0.5-4+deb7u2_i386 +mffm-fftw-dev_1.7-3_i386 +mffm-timecode-dev_1.6-2_all +mingw-w64-dev_2.0.3-1_all +mingw-w64-i686-dev_2.0.3-1_all +mingw-w64-x86-64-dev_2.0.3-1_all +minpack-dev_19961126+dfsg1-1_i386 +mongodb-dev_1:2.0.6-1.1_i386 +mpi-default-dev_1.0.1_i386 +muroard-dev_0.1.10-2_i386 +neko-dev_1.8.1-6_all +nettle-dev_2.4-3_i386 +network-manager-dev_0.9.4.0-10_i386 +ntfs-3g-dev_1:2012.1.15AR.5-2.1_i386 +ocfs2-tools-dev_1.6.4-1+deb7u1_i386 +ocl-icd-dev_1.3-3_i386 +ocl-icd-opencl-dev_1.3-3_i386 +ocsigen-dev_1.3.4-2_all +octave-pkg-dev_1.0.2_all +ofono-dev_1.6-2_all +okteta-dev_4:4.8.4+dfsg-1_i386 +okular-dev_4:4.8.4-3_i386 +open-vm-tools-dev_2:8.8.0+2012.05.21-724730-1+nmu2_all +openais-dev_1.1.4-4.1_i386 +openbox-dev_3.5.0-7_i386 +openchangeserver-dev_1:1.0-3_i386 +oss4-dev_4.2-build2006-2+deb7u1_all +pacemaker-dev_1.1.7-1_i386 +packaging-dev_0.4_all +paraview-dev_3.14.1-6_i386 +parole-dev_0.2.0.6-1+b1_i386 +parser3-dev_3.4.2-2_i386 +pbs-drmaa-dev_1.0.10-3_i386 +petsc-dev_3.2.dfsg-6_all +php5-dev_5.4.4-14+deb7u9_i386 +pidgin-dev_2.10.9-1~deb7u1_all +pinball-dev_0.3.1-13.1_i386 +planetpenguin-racer-gimp-dev_0.4-5_all +planner-dev_0.14.6-1_i386 +plplot-tcl-dev_5.9.9-5_i386 +plptools-dev_1.0.9-2.4_i386 +plymouth-dev_0.8.5.1-5_i386 +portaudio19-dev_19+svn20111121-1_i386 +postfix-dev_2.9.6-2_all +ppl-dev_0.11.2-8_i386 +ppp-dev_2.4.5-5.1_all +prayer-templates-dev_1.3.4-dfsg1-1_i386 +proftpd-dev_1.3.4a-5+deb7u1_i386 +pslib-dev_0.4.5-3_i386 +publib-dev_0.40-1_i386 +puredata-dev_0.43.2-5_all +pvm-dev_3.4.5-12.5_i386 +pxlib-dev_0.6.5-1_i386 +python-all-dev_2.7.3-4+deb7u1_all +python-apt-dev_0.8.8.2_all +python-cairo-dev_1.8.8-1_all +python-cxx-dev_6.2.4-3_all +python-dbus-dev_1.1.1-1_all +python-dev_2.7.3-4+deb7u1_all +python-egenix-mx-base-dev_3.2.1-1.1_all +python-gamera-dev_3.3.3-2_all +python-gi-dev_3.2.2-2_all +python-gnome2-desktop-dev_2.32.0+dfsg-2_all +python-gnome2-dev_2.28.1+dfsg-1_all +python-gnome2-extras-dev_2.25.3-12_all +python-gobject-2-dev_2.28.6-10_all +python-gobject-dev_3.2.2-2_all +python-greenlet-dev_0.3.1-2.5_i386 +python-gst0.10-dev_0.10.22-3_i386 +python-gtk2-dev_2.24.0-3_all +python-kde4-dev_4:4.8.4-1_all +python-ldb-dev_1:1.1.6-1_i386 +python-openturns-dev_1.0-4_i386 +python-pyorbit-dev_2.24.0-6_all +python-qt4-dev_4.9.3-4_all +python-sip-dev_4.13.3-2_i386 +python-talloc-dev_2.0.7+git20120207-1_i386 +python-webkit-dev_1.1.8-2_all +python2.6-dev_2.6.8-1.1_i386 +python2.7-dev_2.7.3-6+deb7u2_i386 +python3-all-dev_3.2.3-6_all +python3-cairo-dev_1.10.0+dfsg-2_all +python3-cxx-dev_6.2.4-3_all +python3-dev_3.2.3-6_all +python3-sip-dev_4.13.3-2_i386 +python3.2-dev_3.2.3-7_i386 +qmf-dev_1.0.7~2011w23.2-2.1_i386 +qtmobility-dev_1.2.0-3_i386 +r-base-dev_2.15.1-4_all +regina-normal-dev_4.93-1_i386 +resource-agents-dev_1:3.9.2-5+deb7u2_i386 +rhythmbox-dev_2.97-2.1_i386 +rivet-plugins-dev_1.8.0-1_all +roarplaylistd-dev_0.1.1-2_all +robot-player-dev_3.0.2+dfsg-4_all +ruby-dev_1:1.9.3_all +ruby-gnome2-dev_1.1.3-2+b1_i386 +ruby1.8-dev_1.8.7.358-7.1+deb7u1_i386 +ruby1.9.1-dev_1.9.3.194-8.1+deb7u2_i386 +rygel-1.0-dev_0.14.3-2+deb7u1_i386 +samba4-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +sbnc-php-dev_1.2-26_all +science-astronomy-dev_1.0_all +science-dataacquisition-dev_1.0_all +science-engineering-dev_1.0_all +science-highenergy-physics-dev_1.0_all +science-mathematics-dev_1.0_all +science-meteorology-dev_1.0_all +science-nanoscale-physics-dev_1.0_all +science-physics-dev_1.0_all +scim-dev_1.4.13-5_all +sciplot-dev_1.36-15_i386 +selinux-policy-dev_2:2.20110726-12_all +seqan-dev_1.3.1-1_all +sfftw-dev_2.1.5-1_i386 +skalibs-dev_0.47-1_i386 +slurm-drmaa-dev_1.0.4-3_i386 +slurm-llnl-basic-plugins-dev_2.3.4-2+b1_i386 +snappea-dev_3.0d3-22_i386 +spl-dev_1.0~pre6-3.1+b1_i386 +sra-toolkit-libs-dev_2.1.7a-1_i386 +ss-dev_2.0-1.42.5-1.1_i386 +stx-btree-dev_0.8.6-1_all +styx-dev_1.8.0-1.1_i386 +supercollider-dev_1:3.4.5-1wheezy1_i386 +svdrpservice-dev_0.0.4-14_all +sweep-dev_0.9.3-6_all +swish-e-dev_2.4.7-3_i386 +syfi-dev_1.0.0.dfsg-1_all +system-tools-backends-dev_2.10.2-1_all +systemtap-sdt-dev_1.7-1+deb7u1_i386 +tcl-dev_8.5.0-2.1_all +tcl-memchan-dev_2.3-2_i386 +tcl-trf-dev_2.1.4-dfsg1-1_i386 +tcl8.4-dev_8.4.19-5_i386 +tcl8.5-dev_8.5.11-2_i386 +tclcl-dev_1.20-6_all +tclx8.4-dev_8.4.0-3_i386 +tclxml-dev_3.3~svn11-2_i386 +tdom-dev_0.8.3~20080525-3+nmu2_i386 +tesseract-ocr-dev_3.02.01-6_all +tix-dev_8.4.3-4_i386 +tk-dev_8.5.0-2.1_all +tk8.4-dev_8.4.19-5_i386 +tk8.5-dev_8.5.11-2_i386 +tqsllib-dev_2.2-5_i386 +trafficserver-dev_3.0.5-1_i386 +tuxpaint-dev_1:0.9.21-1.1_all +unagi-dev_0.3.3-2_i386 +unixodbc-dev_2.2.14p2-5_i386 +uthash-dev_1.9.5-1_all +uuid-dev_2.20.1-5.3_i386 +vdr-dev_1.7.28-1_all +vflib3-dev_3.6.14.dfsg-3+b1_i386 +voms-dev_2.0.8-1_i386 +vstream-client-dev_1.2-6.1_i386 +wcslib-dev_4.13.4-1_i386 +weechat-dev_0.3.8-1+deb7u1_all +wireshark-dev_1.8.2-5wheezy10_i386 +witty-dev_3.2.1-2_all +wordnet-dev_1:3.0-29_i386 +wzdftpd-dev_0.8.3-6.2_i386 +x11proto-bigreqs-dev_1:1.1.2-1_all +x11proto-composite-dev_1:0.4.2-2_all +x11proto-core-dev_7.0.23-1_all +x11proto-damage-dev_1:1.2.1-2_all +x11proto-dmx-dev_1:2.3.1-2_all +x11proto-dri2-dev_2.6-2_all +x11proto-fixes-dev_1:5.0-2_all +x11proto-fonts-dev_2.1.2-1_all +x11proto-gl-dev_1.4.15-1_all +x11proto-input-dev_2.2-1_all +x11proto-kb-dev_1.0.6-2_all +x11proto-print-dev_1.0.5-2_all +x11proto-randr-dev_1.3.2-2_all +x11proto-record-dev_1.14.2-1_all +x11proto-render-dev_2:0.11.1-2_all +x11proto-resource-dev_1.2.0-3_all +x11proto-scrnsaver-dev_1.2.2-1_all +x11proto-video-dev_2.3.1-2_all +x11proto-xcmisc-dev_1.2.2-1_all +x11proto-xext-dev_7.2.1-1_all +x11proto-xf86bigfont-dev_1.2.0-3_all +x11proto-xf86dga-dev_2.1-3_all +x11proto-xf86dri-dev_2.1.1-2_all +x11proto-xf86vidmode-dev_2.3.1-2_all +x11proto-xinerama-dev_1.2.1-2_all +xaw3dg-dev_1.5+E-18.2_i386 +xbmc-eventclients-dev_2:11.0~git20120510.82388d5-1_all +xfce4-panel-dev_4.8.6-4_i386 +xfslibs-dev_3.1.7+b1_i386 +xmhtml1-dev_1.1.7-18_i386 +xmms2-dev_0.8+dfsg-4_all +xorg-dev_1:7.7+3~deb7u1_all +xotcl-dev_1.6.7-2_i386 +xpaint-dev_2.9.1.4-3+b2_i386 +xserver-xorg-dev_2:1.12.4-6+deb7u2_i386 +xserver-xorg-input-evdev-dev_1:2.7.0-1_all +xserver-xorg-input-joystick-dev_1:1.6.1-1_all +xserver-xorg-input-synaptics-dev_1.6.2-2_all +xtrans-dev_1.2.7-1_all +xulrunner-dev_24.4.0esr-1~deb7u2_i386 +xutils-dev_1:7.7~1_i386 +xviewg-dev_3.2p1.4-28.1_i386 +yate-dev_4.1.0-1~dfsg-3_i386 +yorick-dev_2.2.02+dfsg-6_i386 +zathura-dev_0.1.2-4_all +zlib1g-dev_1:1.2.7.dfsg-13_i386 +znc-dev_0.206-2_i386 +zsh-dev_4.3.17-1_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot2Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot2Test_gold new file mode 100644 index 00000000..f7073402 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot2Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: snapshot with name snapshot-xx not found diff --git a/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot3Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot3Test_gold new file mode 100644 index 00000000..58f09611 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot3Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: parsing failed: unexpected token : expecting ')' diff --git a/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot4Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot4Test_gold new file mode 100644 index 00000000..007f3b4f --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/SearchSnapshot4Test_gold @@ -0,0 +1,95 @@ + +coreutils_8.13-3.5_amd64 +coreutils_8.13-3.5_i386 +debconf_1.5.49_all +dpkg_1.16.12_amd64 +dpkg_1.16.12_i386 +fontconfig-config_2.9.0-7.1_all +fonts-freefont-ttf_20120503-1_all +gcc-4.7-base_4.7.2-5_amd64 +gcc-4.7-base_4.7.2-5_i386 +gsfonts-x11_0.22_all +gsfonts_1:8.11+urwcyr1.0.7~pre44-4.2_all +libacl1_2.2.51-8_amd64 +libacl1_2.2.51-8_i386 +libattr1_1:2.4.46-8_amd64 +libattr1_1:2.4.46-8_i386 +libbz2-1.0_1.0.6-4_amd64 +libbz2-1.0_1.0.6-4_i386 +libc-bin_2.13-38+deb7u1_amd64 +libc-bin_2.13-38+deb7u1_i386 +libc6_2.13-38+deb7u1_amd64 +libc6_2.13-38+deb7u1_i386 +libexpat1_2.1.0-1+deb7u1_amd64 +libexpat1_2.1.0-1+deb7u1_i386 +libfontconfig1_2.9.0-7.1_amd64 +libfontconfig1_2.9.0-7.1_i386 +libfontenc1_1:1.1.1-1_amd64 +libfontenc1_1:1.1.1-1_i386 +libfreetype6_2.4.9-1.1_amd64 +libfreetype6_2.4.9-1.1_i386 +libgcc1_1:4.7.2-5_amd64 +libgcc1_1:4.7.2-5_i386 +libgcrypt11_1.5.0-5+deb7u1_amd64 +libgcrypt11_1.5.0-5+deb7u1_i386 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_i386 +libgeoip1_1.4.8+dfsg-3_amd64 +libgeoip1_1.4.8+dfsg-3_i386 +libgpg-error0_1.10-3.1_amd64 +libgpg-error0_1.10-3.1_i386 +libjpeg8_8d-1_amd64 +libjpeg8_8d-1_i386 +liblzma5_5.1.1alpha+20120614-2_amd64 +liblzma5_5.1.1alpha+20120614-2_i386 +libpam0g_1.1.3-7.1_amd64 +libpam0g_1.1.3-7.1_i386 +libpcre3_1:8.30-5_amd64 +libpcre3_1:8.30-5_i386 +libpng12-0_1.2.49-1_amd64 +libpng12-0_1.2.49-1_i386 +libselinux1_2.1.9-5_amd64 +libselinux1_2.1.9-5_i386 +libssl1.0.0_1.0.1e-2+deb7u7_amd64 +libssl1.0.0_1.0.1e-2+deb7u7_i386 +libx11-6_2:1.5.0-1+deb7u1_amd64 +libx11-6_2:1.5.0-1+deb7u1_i386 +libx11-data_2:1.5.0-1+deb7u1_all +libxau6_1:1.0.7-1_amd64 +libxau6_1:1.0.7-1_i386 +libxcb1_1.8.1-2+deb7u1_amd64 +libxcb1_1.8.1-2+deb7u1_i386 +libxdmcp6_1:1.1.1-1_amd64 +libxdmcp6_1:1.1.1-1_i386 +libxfont1_1:1.4.5-3_amd64 +libxfont1_1:1.4.5-3_i386 +libxml2_2.8.0+dfsg1-7+nmu3_amd64 +libxml2_2.8.0+dfsg1-7+nmu3_i386 +libxpm4_1:3.5.10-1_amd64 +libxpm4_1:3.5.10-1_i386 +libxslt1.1_1.1.26-14.1_amd64 +libxslt1.1_1.1.26-14.1_i386 +lsb-base_4.1+Debian8+deb7u1_all +multiarch-support_2.13-38+deb7u1_amd64 +multiarch-support_2.13-38+deb7u1_i386 +nginx-common_1.2.1-2.2+wheezy2_all +nginx-full_1.2.1-2.2+wheezy2_amd64 +nginx-full_1.2.1-2.2+wheezy2_i386 +nginx-light_1.2.1-2.2+wheezy2_amd64 +nginx-light_1.2.1-2.2+wheezy2_i386 +nginx_1.2.1-2.2+wheezy2_all +perl-base_5.14.2-21+deb7u1_amd64 +perl-base_5.14.2-21+deb7u1_i386 +tar_1.26+dfsg-0.1_amd64 +tar_1.26+dfsg-0.1_i386 +ttf-bitstream-vera_1.10-8_all +ttf-dejavu-core_2.33-3_all +ucf_3.0025+nmu3_all +x11-common_1:7.7+3~deb7u1_all +xfonts-encodings_1:1.0.4-1_all +xfonts-utils_1:7.7~1_amd64 +xfonts-utils_1:7.7~1_i386 +zlib1g_1:1.2.7.dfsg-13_amd64 +zlib1g_1:1.2.7.dfsg-13_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot4Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot4Test_gold index 7511132a..1d6c02ce 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot4Test_gold +++ b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot4Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (1742): +Missing dependencies (1737): 915resolution [i386] 9fonts [i386] abakus [i386] @@ -74,7 +74,6 @@ Missing dependencies (1742): big-blast [i386] bigdft [i386] bigsdb [i386] - bind [i386] bioclipse [i386] bioimagesuite [i386] bioimagexd [i386] @@ -205,7 +204,6 @@ Missing dependencies (1742): elmer-doc [i386] elph [i386] emacs-wiki [i386] - emacs22 [i386] emacs23-common-non-dfsg [i386] emacspeak-ss [i386] embassy [i386] @@ -576,7 +574,6 @@ Missing dependencies (1742): kbackgammon [i386] kbdcontrol [i386] kchart [i386] - kde [i386] kde-icons-crystal [i386] kde-icons-oxygen [i386] kde-l10n (>= 4:4.8.4) [i386] @@ -1644,7 +1641,6 @@ Missing dependencies (1742): ttf-larabie-deco [i386] ttf-larabie-straight [i386] ttf-mscorefonts-installer [i386] - ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [i386] tuxpaint (= 1:0.9.21) [i386] @@ -1726,7 +1722,6 @@ Missing dependencies (1742): xmind [i386] xnat [i386] xorsa [i386] - xpdf-reader [i386] xsidplay [i386] xtranslate [i386] xul-ext-gnome-keyring [i386] diff --git a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot5Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot5Test_gold index a13ee544..39e0af1c 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot5Test_gold +++ b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot5Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (1622): +Missing dependencies (1617): 915resolution [i386] 9fonts [i386] abakus [i386] @@ -71,7 +71,6 @@ Missing dependencies (1622): big-blast [i386] bigdft [i386] bigsdb [i386] - bind [i386] bioclipse [i386] bioimagesuite [i386] bioimagexd [i386] @@ -188,7 +187,6 @@ Missing dependencies (1622): elexis [i386] elph [i386] emacs-wiki [i386] - emacs22 [i386] emacspeak-ss [i386] embassy [i386] embassy-phylip [i386] @@ -515,7 +513,6 @@ Missing dependencies (1622): kbackgammon [i386] kbdcontrol [i386] kchart [i386] - kde [i386] kde-icons-crystal [i386] kde-icons-oxygen [i386] kde-l10n (>= 4:4.8.4) [i386] @@ -1535,7 +1532,6 @@ Missing dependencies (1622): tripal [i386] trnascan-se [i386] tscope [i386] - ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [i386] tuxpaint (= 1:0.9.21) [i386] @@ -1607,7 +1603,6 @@ Missing dependencies (1622): xmind [i386] xnat [i386] xorsa [i386] - xpdf-reader [i386] xsidplay [i386] xtranslate [i386] xul-ext-gnome-keyring [i386] diff --git a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot6Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot6Test_gold index af5b29d8..f4083476 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot6Test_gold +++ b/src/github.com/smira/aptly/system/t05_snapshot/VerifySnapshot6Test_gold @@ -1,6 +1,6 @@ Loading packages... Verifying... -Missing dependencies (3797): +Missing dependencies (3781): 915resolution [amd64] 915resolution [i386] 9fonts [amd64] @@ -51,8 +51,6 @@ Missing dependencies (3797): angband-audio [i386] aolserver-doc (>= 4.0.1-1) [amd64] aolserver-doc (>= 4.0.1-1) [i386] - apache [amd64] - apache [i386] apache-ssl [amd64] apache-ssl [i386] ape [amd64] @@ -161,8 +159,6 @@ Missing dependencies (3797): bigdft [i386] bigsdb [amd64] bigsdb [i386] - bind [amd64] - bind [i386] bioclipse [amd64] bioclipse [i386] bioimagesuite [amd64] @@ -458,8 +454,6 @@ Missing dependencies (3797): elph [i386] emacs-wiki [amd64] emacs-wiki [i386] - emacs22 [amd64] - emacs22 [i386] emacs23-common-non-dfsg [amd64] emacs23-common-non-dfsg [i386] emacspeak-ss [amd64] @@ -1264,8 +1258,6 @@ Missing dependencies (3797): kbdcontrol [i386] kchart [amd64] kchart [i386] - kde [amd64] - kde [i386] kde-i18n-he [amd64] kde-i18n-he [i386] kde-icons-crystal [amd64] @@ -2192,8 +2184,6 @@ Missing dependencies (3797): mesquite [i386] metadisorder [amd64] metadisorder [i386] - metamail [amd64] - metamail [i386] metarep [amd64] metarep [i386] mga-vid-module [amd64] @@ -3508,8 +3498,6 @@ Missing dependencies (3797): tc-utils [i386] tempo [amd64] tempo [i386] - tetex-bin [amd64] - tetex-bin [i386] tetra [amd64] tetra [i386] texinfo-doc-nonfree [amd64] @@ -3590,8 +3578,6 @@ Missing dependencies (3797): ttf-larabie-straight [i386] ttf-mscorefonts-installer [amd64] ttf-mscorefonts-installer [i386] - ttf-thryomanes [amd64] - ttf-thryomanes [i386] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [amd64] ttf-ubuntu-font-family (>= 0.80-0ubuntu1~medium) [i386] ttf2pt1 [amd64] @@ -3760,8 +3746,6 @@ Missing dependencies (3797): xnat [i386] xorsa [amd64] xorsa [i386] - xpdf-reader [amd64] - xpdf-reader [i386] xsidplay [amd64] xsidplay [i386] xtranslate [amd64] diff --git a/src/github.com/smira/aptly/system/t05_snapshot/__init__.py b/src/github.com/smira/aptly/system/t05_snapshot/__init__.py index 9b5bbd19..4e1995e9 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/__init__.py +++ b/src/github.com/smira/aptly/system/t05_snapshot/__init__.py @@ -11,3 +11,5 @@ from .diff import * from .merge import * from .drop import * from .rename import * +from .search import * +from .filter import * \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t05_snapshot/filter.py b/src/github.com/smira/aptly/system/t05_snapshot/filter.py new file mode 100644 index 00000000..340748e7 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/filter.py @@ -0,0 +1,98 @@ +from lib import BaseTest +import re + + +class FilterSnapshot1Test(BaseTest): + """ + filter snapshot: simple filter + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-non-free", + ] + runCmd = "aptly snapshot filter snap1 snap2 mame unrar" + + def check(self): + def remove_created_at(s): + return re.sub(r"Created At: [0-9:A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly snapshot show -with-packages snap2", "snapshot_show", match_prepare=remove_created_at) + + +class FilterSnapshot2Test(BaseTest): + """ + filter snapshot: play with versions + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-backports", + ] + runCmd = "aptly snapshot filter -with-deps snap1 snap2 'rsyslog (>= 7.4.4)'" + + def check(self): + def remove_created_at(s): + return re.sub(r"Created At: [0-9:A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly snapshot show -with-packages snap2", "snapshot_show", match_prepare=remove_created_at) + + +class FilterSnapshot3Test(BaseTest): + """ + filter snapshot: complex condition + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-main", + ] + runCmd = "aptly snapshot filter snap1 snap2 'Priority (required)' nginx xyz" + + def check(self): + def remove_created_at(s): + return re.sub(r"Created At: [0-9:A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly snapshot show -with-packages snap2", "snapshot_show", match_prepare=remove_created_at) + + +class FilterSnapshot5Test(BaseTest): + """ + filter snapshot: no such snapshot + """ + fixtureDB = True + fixtureCmds = [ + ] + runCmd = "aptly snapshot filter snap1 snap2 'rsyslog (>= 7.4.4)'" + expectedCode = 1 + + +class FilterSnapshot6Test(BaseTest): + """ + filter snapshot: duplicate snapshot + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-main", + "aptly snapshot merge snap2 snap1", + ] + runCmd = "aptly snapshot filter snap1 snap2 'rsyslog (>= 7.4.4)'" + expectedCode = 1 + + +class FilterSnapshot7Test(BaseTest): + """ + filter snapshot: follow sources + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-backports-src", + ] + runCmd = "aptly -dep-follow-source snapshot filter -with-deps snap1 snap2 'rsyslog (>= 7.4.4), $$Architecture (i386)'" + + def check(self): + def remove_created_at(s): + return re.sub(r"Created At: [0-9:A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly snapshot show --with-packages snap2", "snapshot_show", match_prepare=remove_created_at) diff --git a/src/github.com/smira/aptly/system/t05_snapshot/search.py b/src/github.com/smira/aptly/system/t05_snapshot/search.py new file mode 100644 index 00000000..3a065675 --- /dev/null +++ b/src/github.com/smira/aptly/system/t05_snapshot/search.py @@ -0,0 +1,39 @@ +from lib import BaseTest + + +class SearchSnapshot1Test(BaseTest): + """ + search snapshot: regular search + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"] + runCmd = "aptly snapshot search wheezy-main '$$Architecture (i386), Name (% *-dev)'" + + +class SearchSnapshot2Test(BaseTest): + """ + search snapshot: missing snapshot + """ + runCmd = "aptly snapshot search snapshot-xx 'Name'" + expectedCode = 1 + + +class SearchSnapshot3Test(BaseTest): + """ + search snapshot: wrong expression + """ + fixtureDB = True + fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"] + expectedCode = 1 + runCmd = "aptly snapshot search wheezy-main '$$Architecture (i386'" + + +class SearchSnapshot4Test(BaseTest): + """ + search snapshot: with-deps search + """ + fixtureDB = True + fixtureCmds = ["aptly snapshot create wheezy-main from mirror wheezy-main"] + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly snapshot search -with-deps wheezy-main 'Name (nginx)'" diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_gold index bee36b01..ee9f9d5e 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Local repo local-repo has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo14Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo14Test_gold index 443899a1..c44a11e0 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo14Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo14Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_gold index 443899a1..c44a11e0 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo16Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo16Test_gold index 906bdcea..cbe2b131 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo16Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo16Test_gold @@ -1,6 +1,7 @@ Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag) Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_gold index 707a2715..0ccea7ca 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo18Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo18Test_gold index a105d5fc..c24630e5 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo18Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo18Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_gold index 8b8f3486..365295fa 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo25Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo25Test_gold index 63326118..b65dfd33 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo25Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo25Test_gold @@ -2,6 +2,7 @@ WARNING: force overwrite mode enabled, aptly might corrupt other published repos Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_gold new file mode 100644 index 00000000..365295fa --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_gold @@ -0,0 +1,14 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: + +Local repo local-repo has been successfully published. +Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. +Now you can add following line to apt sources: + deb http://your-server/ maverick main + deb-src http://your-server/ maverick main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_udeb_binary b/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_udeb_binary new file mode 100644 index 00000000..3f13340f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo26Test_udeb_binary @@ -0,0 +1,20 @@ +Package: dmraid-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 36 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb) used by debian-installer +MD5sum: 4d8bb4dafb0ef9059dac75846e162784 +SHA1: fd5c73e08d4c5381b1136c2ff170332d77526246 +SHA256: fe4ff3351186f03039f8cd6f78e8e4f473a75b613f950caac06fa21dda2d59e8 +Source: dmraid +Size: 11022 +Depends: libc6-udeb (>= 2.11), libdmraid1.0.0.rc16-udeb (>= 1.0.0.rc16), dmsetup-udeb +Filename: pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_gold new file mode 100644 index 00000000..365295fa --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_gold @@ -0,0 +1,14 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: + +Local repo local-repo has been successfully published. +Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. +Now you can add following line to apt sources: + deb http://your-server/ maverick main + deb-src http://your-server/ maverick main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_udeb_binary b/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_udeb_binary new file mode 100644 index 00000000..dbfc3986 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo27Test_udeb_binary @@ -0,0 +1,20 @@ +Package: dmraid-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 36 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb) used by debian-installer +MD5sum: 4d8bb4dafb0ef9059dac75846e162784 +SHA1: fd5c73e08d4c5381b1136c2ff170332d77526246 +SHA256: fe4ff3351186f03039f8cd6f78e8e4f473a75b613f950caac06fa21dda2d59e8 +Filename: pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb +Size: 11022 +Source: dmraid +Depends: libc6-udeb (>= 2.11), libdmraid1.0.0.rc16-udeb (>= 1.0.0.rc16), dmsetup-udeb + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo2Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo2Test_gold index 443899a1..c44a11e0 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo2Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo2Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo3Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo3Test_gold index 4aa7a0e2..0b9fb7c9 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo3Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo3Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo4Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishRepo4Test_gold index 0e0a9c7b..82b3864d 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo4Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo4Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_gold index 848cd407..e9a324b8 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap13 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_gold index e0fafd8c..b5b73ebe 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap15 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_gold index e7518879..0befa253 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_gold index a1d7e456..66bc66b4 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot19Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot19Test_gold index 17f563c4..4546c238 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot19Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot19Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap5 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_gold index f21e3054..8cf10f19 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 new file mode 100644 index 00000000..fe39b007 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 @@ -0,0 +1,109 @@ +Package: gnuplot +Version: 4.6.1-1~maverick2 +Installed-Size: 20 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: all +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for transition and to install a full-featured gnuplot + supporting the X11-output. +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 +Source: +Size: 1046 +Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) + +Package: gnuplot-doc +Version: 4.6.1-1~maverick2 +Installed-Size: 5572 +Priority: optional +Section: doc +Maintainer: Debian Science Team +Architecture: all +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the additional documentation. +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 +Source: gnuplot +Depends: dpkg (>= 1.15.4) | install-info +Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Size: 2675242 + +Package: gnuplot-nox +Version: 4.6.1-1~maverick2 +Installed-Size: 2624 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: amd64 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for working without an X server. +MD5sum: db55daca818697b23024255e536399da +SHA1: d5a1b0bbfb562e5cecef3f3fb70ddb4cd6103507 +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb +Replaces: gnuplot (<< 4.0.0) +Source: gnuplot +Recommends: groff, ttf-liberation +Size: 1129114 +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) + +Package: gnuplot-x11 +Version: 4.6.1-1~maverick2 +Installed-Size: 1716 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: amd64 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. +MD5sum: 17ab6787992b979e3a4851a90dfaf0a8 +SHA1: d60b0ee30a885ba0202adddccd7968ab70be7426 +Size: 819248 +Source: gnuplot +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Replaces: gnuplot (<< 4.0.0) +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 new file mode 100644 index 00000000..1c5127de --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 @@ -0,0 +1,109 @@ +Package: gnuplot +Version: 4.6.1-1~maverick2 +Installed-Size: 20 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: all +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for transition and to install a full-featured gnuplot + supporting the X11-output. +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 +Size: 1046 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Source: + +Package: gnuplot-doc +Version: 4.6.1-1~maverick2 +Installed-Size: 5572 +Priority: optional +Section: doc +Maintainer: Debian Science Team +Architecture: all +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the additional documentation. +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 +Source: gnuplot +Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Depends: dpkg (>= 1.15.4) | install-info +Size: 2675242 + +Package: gnuplot-nox +Version: 4.6.1-1~maverick2 +Installed-Size: 2536 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: i386 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for working without an X server. +MD5sum: a7ef16004b62fd78acb77edb058ea1c1 +SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d +Size: 1046496 +Source: gnuplot +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Recommends: groff, ttf-liberation +Replaces: gnuplot (<< 4.0.0) +Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb + +Package: gnuplot-x11 +Version: 4.6.1-1~maverick2 +Installed-Size: 1604 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: i386 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Source: gnuplot +Replaces: gnuplot (<< 4.0.0) +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Size: 724388 + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot20Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot20Test_gold index 1ff04477..648d37fb 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot20Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot20Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap3 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot22Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot22Test_gold index 1ff04477..648d37fb 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot22Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot22Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap3 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot23Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot23Test_gold index 1ff04477..648d37fb 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot23Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot23Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Snapshot snap3 has been successfully published. Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_gold index 92859d40..b951722b 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot25Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot25Test_gold index 1b95a2eb..6e087c6e 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot25Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot25Test_gold @@ -1,6 +1,7 @@ Warning: publishing from empty source, architectures list should be complete, it can't be changed after publishing (use -architectures flag) Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_gold index b7def77e..b59e5089 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot27Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot27Test_gold index 604b8d09..3e4d4262 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot27Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot27Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_gold index 950aab8b..045b14e7 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot34Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot34Test_gold index 2a851c89..aa3529d9 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot34Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot34Test_gold @@ -2,6 +2,7 @@ WARNING: force overwrite mode enabled, aptly might corrupt other published repos Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_gold new file mode 100644 index 00000000..ad5dad8d --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_gold @@ -0,0 +1,13 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: + +Snapshot squeeze has been successfully published. +Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing. +Now you can add following line to apt sources: + deb http://your-server/ squeeze main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 new file mode 100644 index 00000000..32ad0b4b --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 @@ -0,0 +1,88 @@ +Package: dmraid +Version: 1.0.0.rc16-4.1 +Installed-Size: 112 +Priority: optional +Section: admin +Maintainer: Giuseppe Iuculano +Architecture: amd64 +Description: Device-Mapper Software RAID support tool + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + The following formats are supported: + Highpoint HPT37X/HPT45X + Intel Software RAID + LSI Logic MegaRAID + NVidia NForce RAID (nvraid) + Promise FastTrack + Silicon Image(tm) Medley(tm) + VIA Software RAID + . + Please read the documentation in /usr/share/doc/dmraid BEFORE attempting + any use of this software. Improper use can cause data loss! +MD5sum: 35da9bcdd12c7fb08eb7192f0a17ddf2 +SHA1: 6a89d3f9e3b80a172811bb7d74eac43f119a8b7c +SHA256: 125405c4b0a7364bf209c161f393d4d0152ba9d02a55a95d90a7637f7b373b8f +Tag: admin::filesystem, admin::kernel, hardware::storage, implemented-in::c, interface::commandline, role::program, scope::utility, use::scanning +Size: 38620 +Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ +Source: +Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_amd64.deb + +Package: libdmraid-dev +Version: 1.0.0.rc16-4.1 +Installed-Size: 496 +Priority: optional +Section: libdevel +Maintainer: Giuseppe Iuculano +Architecture: amd64 +Description: Device-Mapper Software RAID support tool - header files + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + This package contains the header files needed to link programs against + dmraid. +MD5sum: bb209b5796592d786c28844b949216dc +SHA1: cd8baba807fa92a88a265a044d821df8b677b5cb +SHA256: 081a48ad5372a941c35d41733da89a52cbe2d8f49032c2a4ef03148e4049615f +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ +Tag: admin::hardware, devel::lang:c, devel::library, hardware::storage, implemented-in::c, qa::low-popcon, role::devel-lib, use::driver +Size: 152618 +Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) +Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_amd64.deb +Source: dmraid + +Package: libdmraid1.0.0.rc16 +Version: 1.0.0.rc16-4.1 +Installed-Size: 244 +Priority: optional +Section: libs +Maintainer: Giuseppe Iuculano +Architecture: amd64 +Description: Device-Mapper Software RAID support tool - shared library + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + This package contains the dmraid shared library, which implements + the back half of dmraid, including on-disk metadata formats. +MD5sum: a66d03bb1ddad78f879660ddedf86295 +SHA1: 6292936617c466e67a3148c66d0c27c068d055d3 +SHA256: 29f06bd3ae42e3380b356b69598be07724d178af35f2f1a64648c7f8ff85bef9 +Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ +Size: 108978 +Tag: admin::hardware, admin::kernel, devel::lang:c, devel::library, hardware::storage, implemented-in::c, role::{devel-lib,kernel,shared-lib}, use::driver +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_amd64.deb +Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) +Source: dmraid + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 new file mode 100644 index 00000000..be1c153c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 @@ -0,0 +1,88 @@ +Package: libdmraid1.0.0.rc16 +Version: 1.0.0.rc16-4.1 +Installed-Size: 268 +Priority: optional +Section: libs +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool - shared library + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + This package contains the dmraid shared library, which implements + the back half of dmraid, including on-disk metadata formats. +MD5sum: 9330ba2ffd2f22d695fdf692f8120159 +SHA1: 6b262419836e8cad4500043f5e9e6a1581074023 +SHA256: 2b2238679ac8ff4776a3a2caf533c551700d9f92a7d2af23d6457acf7de5d6c8 +Tag: admin::hardware, admin::kernel, devel::lang:c, devel::library, hardware::storage, implemented-in::c, role::{devel-lib,kernel,shared-lib}, use::driver +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_i386.deb +Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ +Source: dmraid +Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) +Size: 106088 + +Package: dmraid +Version: 1.0.0.rc16-4.1 +Installed-Size: 176 +Priority: optional +Section: admin +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + The following formats are supported: + Highpoint HPT37X/HPT45X + Intel Software RAID + LSI Logic MegaRAID + NVidia NForce RAID (nvraid) + Promise FastTrack + Silicon Image(tm) Medley(tm) + VIA Software RAID + . + Please read the documentation in /usr/share/doc/dmraid BEFORE attempting + any use of this software. Improper use can cause data loss! +MD5sum: f8aea4e9eaea341b112f02e9efe1678e +SHA1: bb96a258038c79bc04eef49d5875deed4c67dd16 +SHA256: 6a8294bef99040055009da41597869bfdb17ac89c3166e49c57340abe7f702ba +Size: 37984 +Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup +Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_i386.deb +Tag: admin::filesystem, admin::kernel, hardware::storage, implemented-in::c, interface::commandline, role::program, scope::utility, use::scanning +Source: +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ + +Package: libdmraid-dev +Version: 1.0.0.rc16-4.1 +Installed-Size: 440 +Priority: optional +Section: libdevel +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool - header files + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + This package contains the header files needed to link programs against + dmraid. +MD5sum: 5395970df02ab5f1609cd7eccc15ead1 +SHA1: f27bd38eeb58a32ee7e58ac8a2950649bd4ef17b +SHA256: 2abe9142ce6aa341df57303b5bc847522779ea9109b0fe734e2ae4419872da71 +Tag: admin::hardware, devel::lang:c, devel::library, hardware::storage, implemented-in::c, qa::low-popcon, role::devel-lib, use::driver +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ +Size: 145808 +Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_i386.deb +Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) +Source: dmraid + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_amd64 new file mode 100644 index 00000000..45ca5fd0 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_amd64 @@ -0,0 +1,40 @@ +Package: dmraid-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 32 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: amd64 +Description: Device-Mapper Software RAID support tool (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb) used by debian-installer +MD5sum: 721685fde18001ad0c9ac172c3118983 +SHA1: 88e229b76cb5866c8868a491a6690b3fde2b33d5 +SHA256: efae69921b97494e40437712053b60a5105fa433f3cfbae3bb2991d341eb95a6 +Filename: pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb +Depends: libc6-udeb (>= 2.11), libdmraid1.0.0.rc16-udeb (>= 1.0.0.rc16), dmsetup-udeb +Source: dmraid +Size: 11806 + +Package: libdmraid1.0.0.rc16-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 0 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: amd64 +Description: Device-Mapper Software RAID support tool - shared library (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb shared library) used by debian-installer +MD5sum: efae3ee2d1ccd78aaec7d452ecba4c6a +SHA1: 2ef8c01a0375c92f59fed32949b9469cc53d0b99 +SHA256: aabf098de9fcf2da0c0f66f2d9f1cb61f7e244dd2b009361e40cd29827749d44 +Size: 92372 +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16-udeb_1.0.0.rc16-4.1_amd64.udeb +Source: dmraid +Depends: libc6-udeb (>= 2.11), libdevmapper1.02.1-udeb (>= 2:1.02.48) + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_i386 new file mode 100644 index 00000000..0f65046d --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_udeb_i386 @@ -0,0 +1,40 @@ +Package: dmraid-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 36 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb) used by debian-installer +MD5sum: 4d8bb4dafb0ef9059dac75846e162784 +SHA1: fd5c73e08d4c5381b1136c2ff170332d77526246 +SHA256: fe4ff3351186f03039f8cd6f78e8e4f473a75b613f950caac06fa21dda2d59e8 +Source: dmraid +Size: 11022 +Filename: pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb +Depends: libc6-udeb (>= 2.11), libdmraid1.0.0.rc16-udeb (>= 1.0.0.rc16), dmsetup-udeb + +Package: libdmraid1.0.0.rc16-udeb +Version: 1.0.0.rc16-4.1 +Installed-Size: 212 +Priority: optional +Section: debian-installer +Maintainer: Giuseppe Iuculano +Architecture: i386 +Description: Device-Mapper Software RAID support tool - shared library (udeb) + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + This is the minimal package (udeb shared library) used by debian-installer +MD5sum: aba78093c15c8bcd8e237f6a578c6c65 +SHA1: c5e95d443889775a48d6c48bf332a21a37ce63c6 +SHA256: 1c51dbf4cd1a5a683fd60e2b4f44dc6f8f574de3aea52354541a9a105f10f918 +Depends: libc6-udeb (>= 2.11), libdevmapper1.02.1-udeb (>= 2:1.02.48) +Source: dmraid +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16-udeb_1.0.0.rc16-4.1_i386.udeb +Size: 89490 + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release new file mode 100644 index 00000000..2e1afbf0 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release @@ -0,0 +1,58 @@ +Origin: . squeeze +Label: . squeeze +Codename: squeeze +Date: Tue, 30 Sep 2014 15:35:22 UTC +Architectures: amd64 i386 +Components: main +Description: Generated by aptly +MD5Sum: + a75ee7a5106ba4369de928e26b7afefd 803 main/debian-installer/binary-i386/Packages.bz2 + d82f063b0a674ee60d070fc960c33c92 677 main/debian-installer/binary-amd64/Packages.gz + 8b51fb682910e0d52caa31b61ef1192a 807 main/debian-installer/binary-amd64/Packages.bz2 + a77ec46f63b69e32fdf3a5aa484c1190 1592 main/binary-i386/Packages.bz2 + 9efff4ebb46b70b71215a8df4f71069d 88 main/binary-amd64/Release + d9d38d0cff22f7364cbabb4e8b536316 87 main/debian-installer/binary-i386/Release + 0eaacc9b677879735bcc958c2e24c699 1395 main/binary-i386/Packages.gz + e1c910470349056521dbc4d473a48637 677 main/debian-installer/binary-i386/Packages.gz + d9d38d0cff22f7364cbabb4e8b536316 87 main/binary-i386/Release + 1093e4c5170235ac5cc872f985088815 3669 main/binary-amd64/Packages + c4b9d1069fcb04fdad832a657ff02ef3 3663 main/binary-i386/Packages + b58a784bc0764d523fd9134b53c8dda0 1585 main/binary-amd64/Packages.bz2 + 9ac58b6597a8e0344d69c2550aca9720 1601 main/debian-installer/binary-i386/Packages + f940214380907f004b1e175a6c20bf07 1603 main/debian-installer/binary-amd64/Packages + 9efff4ebb46b70b71215a8df4f71069d 88 main/debian-installer/binary-amd64/Release + 703b425641f4e847a1f0a8a0c28fb128 1394 main/binary-amd64/Packages.gz +SHA1: + a0c5944608dc219fad9d799b3fa6aae280d331c0 803 main/debian-installer/binary-i386/Packages.bz2 + 5faf018385934f65a6af0c4ab3af2fda62c63aff 677 main/debian-installer/binary-amd64/Packages.gz + 61c9b82f75a642839e6e32e5a734f890417b1160 807 main/debian-installer/binary-amd64/Packages.bz2 + e69414d40bb79bca8dc1b274ceb42fb04c3d02ee 1592 main/binary-i386/Packages.bz2 + 7c25a15429615225e3eb90540ba783561fc09448 88 main/binary-amd64/Release + f07fcb0797d81341b6284ed86e5903dc57341a90 87 main/debian-installer/binary-i386/Release + a8657c2409859da9f91280a5da48f3b5276e2829 1395 main/binary-i386/Packages.gz + b8e5b5b41a6ded99006a94c0550cd2291ac19d7f 677 main/debian-installer/binary-i386/Packages.gz + f07fcb0797d81341b6284ed86e5903dc57341a90 87 main/binary-i386/Release + 0c86f7bd6ed2b52b0ab12ea08a76d14235b85d7c 3669 main/binary-amd64/Packages + 4227cdcd3260e10eee066182f22ec8eec4fc7f0a 3663 main/binary-i386/Packages + 8cec67723e4cee24f67ffa46a1f4ae7165fb31f0 1585 main/binary-amd64/Packages.bz2 + ae94f4b0b3396951399de65e04784ef7b0f95119 1601 main/debian-installer/binary-i386/Packages + 6f8e5137388e594b31bed56ca9e08f8e9f305ca4 1603 main/debian-installer/binary-amd64/Packages + 7c25a15429615225e3eb90540ba783561fc09448 88 main/debian-installer/binary-amd64/Release + 163a7a656c5e338d53bbc6cbe80263ca551dfa15 1394 main/binary-amd64/Packages.gz +SHA256: + 4f8eeab36071b8791ce74099df89e01d46ab66f3c76dd9afe6c31fe48c30783d 803 main/debian-installer/binary-i386/Packages.bz2 + bf7b96d1c66abb7dc6037299ab4fe0119d42b66c8c01cfa0520e27d813c99e50 677 main/debian-installer/binary-amd64/Packages.gz + 3a30d9da1ed1108d3451c0c7fe60d99594a2cdf2459a8e505920ed69043bdc6c 807 main/debian-installer/binary-amd64/Packages.bz2 + 1d947dcc40ad2ace3b8226b68161948478a187eb9865d4b62c5068200e0ec058 1592 main/binary-i386/Packages.bz2 + e8378aced6fec291729f656e1d884225ec9c28ba67fc434ef2531223bc37033e 88 main/binary-amd64/Release + 62b9292134aefb30a75aff3e25c2c694d128d73a1d193f29a397789dd902a854 87 main/debian-installer/binary-i386/Release + e30a8b568654e69f1fe7744ace4ffb0d385a8e52502ffd9f84a8184130386a08 1395 main/binary-i386/Packages.gz + f6f2350eab308eb2f290b98f088e973e70ded5d1244688b71edfb201ac85e832 677 main/debian-installer/binary-i386/Packages.gz + 62b9292134aefb30a75aff3e25c2c694d128d73a1d193f29a397789dd902a854 87 main/binary-i386/Release + e2d936cb65a504e6bf13bb09c5a0c6e8943cdd7845d715d571b1fb58262a624f 3669 main/binary-amd64/Packages + 14ae70d15fa8263b55056ef36bac9208ee9e03847118788cc00b6d2a46b5fa10 3663 main/binary-i386/Packages + 0128db3912e0e2f92b2e3a277c28239d6e072323b35bc007dbf32bc696df413c 1585 main/binary-amd64/Packages.bz2 + c3f2708d36c503619f5b3f43b2c7da3f559b72f723c96d0ce9c664f92c6fcc14 1601 main/debian-installer/binary-i386/Packages + 1f90f76bc0df9a588940d14f3ee0ad7d26a86809537f2e5ff4d340e4a8a21f3d 1603 main/debian-installer/binary-amd64/Packages + e8378aced6fec291729f656e1d884225ec9c28ba67fc434ef2531223bc37033e 88 main/debian-installer/binary-amd64/Release + e179f48a91a8dc614a37e2fb21d8d82ff3937fd44e077ec0e2507b8382d896ab 1394 main/binary-amd64/Packages.gz diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 new file mode 100644 index 00000000..77d3c28c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 @@ -0,0 +1,5 @@ +Origin: . squeeze +Label: . squeeze +Architecture: i386 +Archive: squeeze +Component: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_gold index 0b1abe52..ce8cd03c 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_gold index 817f40ae..f4fe3441 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot5Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot5Test_gold index 67cf0fc2..11b57889 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot5Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot5Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch11Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch11Test_gold index 038992dd..2385a0ed 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch11Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch11Test_gold @@ -2,6 +2,7 @@ WARNING: force overwrite mode enabled, aptly might corrupt other published repos Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_gold index 4718a447..3369aa06 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_gold index 43468d71..c8464423 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "ppa" components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch3Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch3Test_gold index 4718a447..3369aa06 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch3Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch3Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch4Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch4Test_gold index 2a0bd499..7256431a 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch4Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch4Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "ppa" components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_gold index e28bc2bb..d9870ff9 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components b, c... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate10Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate10Test_gold index f13cb098..bda2f4dd 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate10Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate10Test_gold @@ -2,6 +2,7 @@ WARNING: force overwrite mode enabled, aptly might corrupt other published repos Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_gold index 9179c19d..72e92234 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate2Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate2Test_gold index 9179c19d..72e92234 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate2Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate2Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate3Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate3Test_gold index 9179c19d..72e92234 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate3Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate3Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate4Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate4Test_gold index 4ebbbebe..dce245d5 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate4Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate4Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate7Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate7Test_gold index 9eb5d807..813d7055 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate7Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate7Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Signing file 'Release' with gpg, please enter your passphrase when prompted: Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: Cleaning up prefix "." components contrib, main... diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate8Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate8Test_gold index 046785d1..09aa9845 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate8Test_gold +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate8Test_gold @@ -1,5 +1,6 @@ Loading packages... Generating metadata files and linking package files... +Finalizing metadata files... Cleaning up prefix "." components contrib, main... Publish for local repo ./squeeze [i386] publishes {contrib: [repo2]}, {main: [repo1]} has been successfully updated. diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_binary b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_binary new file mode 100644 index 00000000..eecd933f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_binary @@ -0,0 +1,25 @@ +Package: libboost-program-options-dev +Version: 1.49.0.1 +Installed-Size: 26 +Priority: optional +Section: libdevel +Maintainer: Debian Boost Team +Architecture: i386 +Description: program options library for C++ (default version) + This package forms part of the Boost C++ Libraries collection. + . + Library to let program developers obtain program options, that is + (name, value) pairs from the user, via conventional methods such as + command line and config file. + . + This package is a dependency package, which depends on Debian's default + Boost version (currently 1.49). +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Size: 2738 +Homepage: http://www.boost.org/libs/program_options/ +Source: boost-defaults +Depends: libboost-program-options1.49-dev + diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_gold b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_gold new file mode 100644 index 00000000..b65701eb --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_gold @@ -0,0 +1,13 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: + +Local repo local-repo has been successfully published. +Now you can add following line to apt sources: + deb http://your-server/ maverick main + deb-src http://your-server/ maverick main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release new file mode 100644 index 00000000..ad1fa6fd --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release @@ -0,0 +1,34 @@ +Origin: . maverick +Label: . maverick +Codename: maverick +Date: Wed, 1 Oct 2014 08:48:48 UTC +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: + b844530d1336e9a3c431f0d36cfc01b0 602 main/binary-i386/Packages.gz + 1d7829dac8923aafe499f313abfaadd7 652 main/binary-i386/Packages.bz2 + 307b6495eab59c221e2ff8962896631b 2300 main/source/Sources + 65dd7338cfac70762457b586629e87e4 839 main/source/Sources.gz + 5cc219da21fdb8a96b265bca1c4c0808 1009 main/source/Sources.bz2 + 60b30b7b0c62ae04bb3bc457abadaced 90 main/binary-i386/Release + 945211dc923a8d1b97835232648c0aa7 92 main/source/Release + d419bd11e2b7fe9669bccdf67a18ca17 984 main/binary-i386/Packages +SHA1: + 1b314cedcf18a6d08d4aabbd8b9b5605ba293d04 602 main/binary-i386/Packages.gz + 5406a984c100b20fbebacdbac24ae3378885f73b 652 main/binary-i386/Packages.bz2 + e30d7bc51cd042ee987316967bf3043ab95c8ce9 2300 main/source/Sources + d60a7032080848eb48bcf68962698ba642dcc383 839 main/source/Sources.gz + fb194b90e0e0efd456a7346c4224294018b6677d 1009 main/source/Sources.bz2 + 2bfef2580deadf6863ee6f893e8b9a2c7522e1ed 90 main/binary-i386/Release + 8b98a2148d157bf87cc1955ef00ba1ba31275f94 92 main/source/Release + be80e1c588c6052f30865e44e3f1429f730d5bc8 984 main/binary-i386/Packages +SHA256: + a079102fdc72e6228229aaa8e5e6ad59b582026419737e81e11a8af2addd125e 602 main/binary-i386/Packages.gz + 25d101a333e85d952afc74f684cef3716d69e3c33d8a4b1544faec683c1b5d96 652 main/binary-i386/Packages.bz2 + bcf1fcf1ca2d1bb5565da8b4c39052d906832ad4885c21682d605b830e55a506 2300 main/source/Sources + 3e6cf6dc079333cdf01905957c611702f4ee10f654c84895ac7bf166bbbbd3bc 839 main/source/Sources.gz + 47b9d37fa81d23d227dd26e85821dd4f74db8f17ddefbe6ca686f62ddfedd8ad 1009 main/source/Sources.bz2 + 1d91164164e6310a5e5fc93390995028956f657490a9ce7aa136dc94291828a8 90 main/binary-i386/Release + 2d75333511325affcefe66c6cfbaa6ab21e6aa0e85a6b4fa39a4191146b81460 92 main/source/Release + 59643cc2d105694d6876dc328290a1c949b4e91e62ee8db396abac83a7034f9f 984 main/binary-i386/Packages diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_sources b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_sources new file mode 100644 index 00000000..7ea3c88c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_sources @@ -0,0 +1,42 @@ +Package: pyspi +Version: 0.6.1-1.3 +Maintainer: Jose Carlos Garcia Sogo +Architecture: any +Binary: python-at-spi +Standards-Version: 3.7.3 +Format: 1.0 +Files: 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + b72cb94699298a117b7c82641c68b6fd 1782 pyspi_0.6.1-1.3.dsc + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz +Checksums-Sha1: 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 56c8a9b1f4ab636052be8966690998cbe865cd6c 1782 pyspi_0.6.1-1.3.dsc + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Homepage: http://people.redhat.com/zcerza/dogtail +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Directory: pool/main/p/pyspi +Checksums-Sha256: 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + d494aaf526f1ec6b02f14c2f81e060a5722d6532ddc760ec16972e45c2625989 1782 pyspi_0.6.1-1.3.dsc + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz + +Package: pyspi +Version: 0.6.1-1.4 +Maintainer: Jose Carlos Garcia Sogo +Architecture: any +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Standards-Version: 3.7.3 +Homepage: http://people.redhat.com/zcerza/dogtail +Directory: pool/main/p/pyspi +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Checksums-Sha256: 289d3aefa970876e9c43686ce2b02f478d7f3ed35a713928464a98d54ae4fca3 893 pyspi-0.6.1-1.3.stripped.dsc + 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz +Format: 1.0 +Checksums-Sha1: 5005fbd1f30637edc1d380b30f45db9b79100d07 893 pyspi-0.6.1-1.3.stripped.dsc + 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz +Binary: python-at-spi +Files: 2f5bd47cf38852b6fc927a50f98c1448 893 pyspi-0.6.1-1.3.stripped.dsc + 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz + diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_binary b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_binary new file mode 100644 index 00000000..05d63c09 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_binary @@ -0,0 +1,25 @@ +Package: libboost-program-options-dev +Version: 1.49.0.1 +Installed-Size: 26 +Priority: optional +Section: libdevel +Maintainer: Debian Boost Team +Architecture: i386 +Description: program options library for C++ (default version) + This package forms part of the Boost C++ Libraries collection. + . + Library to let program developers obtain program options, that is + (name, value) pairs from the user, via conventional methods such as + command line and config file. + . + This package is a dependency package, which depends on Debian's default + Boost version (currently 1.49). +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +Source: boost-defaults +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Depends: libboost-program-options1.49-dev +Homepage: http://www.boost.org/libs/program_options/ +Size: 2738 + diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_gold b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_gold new file mode 100644 index 00000000..12c9c0e6 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_gold @@ -0,0 +1,8 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: +Cleaning up prefix "." components main... + +Publish for local repo s3:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release new file mode 100644 index 00000000..7c89bc78 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release @@ -0,0 +1,34 @@ +Origin: . maverick +Label: . maverick +Codename: maverick +Date: Wed, 1 Oct 2014 09:13:14 UTC +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: + d41d8cd98f00b204e9800998ecf8427e 0 main/source/Sources + f41c10a4b35cd3e1ec8abb9c2ab676ed 23 main/source/Sources.gz + 4059d198768f9f8dc9372dc1c54bc3c3 14 main/source/Sources.bz2 + 60b30b7b0c62ae04bb3bc457abadaced 90 main/binary-i386/Release + 945211dc923a8d1b97835232648c0aa7 92 main/source/Release + db76ccafa3c9e4c1dba620259df78f87 984 main/binary-i386/Packages + d666eb8b2fc8a0ef525d37aff33c7b2f 603 main/binary-i386/Packages.gz + ca2b3a9fc60f4a0a1091b9f0357b11eb 651 main/binary-i386/Packages.bz2 +SHA1: + da39a3ee5e6b4b0d3255bfef95601890afd80709 0 main/source/Sources + 92c6cff562771f64540523a54baaa0b2afe54b3f 23 main/source/Sources.gz + 64a543afbb5f4bf728636bdcbbe7a2ed0804adc2 14 main/source/Sources.bz2 + 2bfef2580deadf6863ee6f893e8b9a2c7522e1ed 90 main/binary-i386/Release + 8b98a2148d157bf87cc1955ef00ba1ba31275f94 92 main/source/Release + 7dcfa6945771369da0a22c2f90f2300b5d238662 984 main/binary-i386/Packages + ba6efb87b17aa8d08476b3f181702e4d3199794e 603 main/binary-i386/Packages.gz + 0b36a014d1a5ccbf3d73de0035970737659e3c0f 651 main/binary-i386/Packages.bz2 +SHA256: + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/source/Sources + 1775fca35fb6a4d31c541746eaea63c5cb3c00280c8b5a351d4e944cdca7489d 23 main/source/Sources.gz + d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/source/Sources.bz2 + 1d91164164e6310a5e5fc93390995028956f657490a9ce7aa136dc94291828a8 90 main/binary-i386/Release + 2d75333511325affcefe66c6cfbaa6ab21e6aa0e85a6b4fa39a4191146b81460 92 main/source/Release + 0e2e7586903004efb49dd419be8a98260dab502352c4b1bf6074f658220aef4e 984 main/binary-i386/Packages + e2bd1d551b4983253cc26004504ead7b6987e609db8cb7185ab3dde69d346acd 603 main/binary-i386/Packages.gz + 81bcd3d47fc3e9dbe1e201d7ec1b356dd2ae3bc5c171f76247243a64755c25d6 651 main/binary-i386/Packages.bz2 diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_sources b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_sources new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_binary b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_binary new file mode 100644 index 00000000..68a4003f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_binary @@ -0,0 +1,29 @@ +Package: gnuplot-x11 +Version: 4.6.1-1~maverick2 +Installed-Size: 1604 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: i386 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Replaces: gnuplot (<< 4.0.0) +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Size: 724388 +Source: gnuplot + diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_gold b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_gold new file mode 100644 index 00000000..b6fc6fce --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_gold @@ -0,0 +1,8 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: +Cleaning up prefix "." components main... + +Publish for snapshot s3:test1:./maverick [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release new file mode 100644 index 00000000..892aa912 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release @@ -0,0 +1,34 @@ +Origin: . maverick +Label: . maverick +Codename: maverick +Date: Wed, 1 Oct 2014 09:16:49 UTC +Architectures: amd64 i386 +Components: main +Description: Generated by aptly +MD5Sum: + 4717e26fc4a8703cd8886feb8ff9532d 91 main/binary-amd64/Release + 60b30b7b0c62ae04bb3bc457abadaced 90 main/binary-i386/Release + 2b810443a56c38746aba877b84fc74a1 1526 main/binary-amd64/Packages + 28bced4c89869001d9fe6b7c553dd1df 862 main/binary-amd64/Packages.gz + aaa2ee36bda75a9c66e31881ae128016 931 main/binary-amd64/Packages.bz2 + aac26f9e4705d03000094f76d475aea2 1524 main/binary-i386/Packages + 158aec0342fc4ca52178b4512c5ee1b5 862 main/binary-i386/Packages.gz + 34859d0bf49cb66045de43d01b1de311 939 main/binary-i386/Packages.bz2 +SHA1: + 93c9982ebbb6a74a118d07e500b596097c8c4780 91 main/binary-amd64/Release + 2bfef2580deadf6863ee6f893e8b9a2c7522e1ed 90 main/binary-i386/Release + 876cafdad8672c4b0b66baec5b12213d2bcb4cf3 1526 main/binary-amd64/Packages + b3e2e9ad945a190e2ce4aeb36d1946d9ad04a075 862 main/binary-amd64/Packages.gz + bc8a7022261b79f5aeacdca551c51aeb7530b969 931 main/binary-amd64/Packages.bz2 + 7eca65cdb4a4a6bcb51747f2c8d4829f4457f22b 1524 main/binary-i386/Packages + e1f5ab02bdd1fcaa0ab93c5680919f612692992c 862 main/binary-i386/Packages.gz + 8a7f311f39316dcedc8a199421116ba92a941028 939 main/binary-i386/Packages.bz2 +SHA256: + 73aa8d6aaf47a1bf3c546869ceb09a882a8c2d840f81878e552fe2d1260ac4e2 91 main/binary-amd64/Release + 1d91164164e6310a5e5fc93390995028956f657490a9ce7aa136dc94291828a8 90 main/binary-i386/Release + f47ca8ea0dc02b4423b1291b302e5594c0ac5c01da72c6f9de1ae17d3eddef2f 1526 main/binary-amd64/Packages + 0a939f23e1ed98ec3cf2033eb5665d4c40e7494d6331f453ac2043be3e234897 862 main/binary-amd64/Packages.gz + abdb8e2537c11272fc9f70ccbcbd2ee867ae797666d3bf11a51972fa2f4d0325 931 main/binary-amd64/Packages.bz2 + 7b1e711ab4647a3e200af742690ffee76bcf7244f597fda699495e29177b1c71 1524 main/binary-i386/Packages + 5723a156f299c657b2eebd1c17ff1a0ca3f50036fc9a1b6c7d9f985a1841c171 862 main/binary-i386/Packages.gz + 41f396a3b5c7f78d743971a1011706c6782c8abac3168ff862fa301255baa040 939 main/binary-i386/Packages.bz2 diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish4Test_gold b/src/github.com/smira/aptly/system/t06_publish/S3Publish4Test_gold new file mode 100644 index 00000000..2d98c85a --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish4Test_gold @@ -0,0 +1,4 @@ +Published repositories: + * s3:test1:./maverick [amd64, i386] publishes {main: [local-repo]} + * s3:test1:./xyz [amd64, i386] publishes {main: [local-repo]} + * s3:test1:prefix/maverick [amd64, i386] publishes {main: [local-repo]} diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish5Test_gold b/src/github.com/smira/aptly/system/t06_publish/S3Publish5Test_gold new file mode 100644 index 00000000..cadce02c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish5Test_gold @@ -0,0 +1,3 @@ +Cleaning up prefix "." components main... + +Published repository has been removed successfully. diff --git a/src/github.com/smira/aptly/system/t06_publish/__init__.py b/src/github.com/smira/aptly/system/t06_publish/__init__.py index 55e1dd3b..45ba80df 100644 --- a/src/github.com/smira/aptly/system/t06_publish/__init__.py +++ b/src/github.com/smira/aptly/system/t06_publish/__init__.py @@ -8,3 +8,4 @@ from .repo import * from .snapshot import * from .switch import * from .update import * +from .s3 import * diff --git a/src/github.com/smira/aptly/system/t06_publish/repo.py b/src/github.com/smira/aptly/system/t06_publish/repo.py index e9f7df3b..b29d4623 100644 --- a/src/github.com/smira/aptly/system/t06_publish/repo.py +++ b/src/github.com/smira/aptly/system/t06_publish/repo.py @@ -584,3 +584,65 @@ class PublishRepo25Test(BaseTest): super(PublishRepo25Test, self).check() self.check_file_contents("public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz", "file") + + +class PublishRepo26Test(BaseTest): + """ + publish repo: sign with passphrase + """ + fixtureCmds = [ + "aptly repo create local-repo", + "aptly repo add local-repo ${files}", + ] + runCmd = "aptly publish repo -keyring=${files}/aptly_passphrase.pub -secret-keyring=${files}/aptly_passphrase.sec -passphrase=verysecret -distribution=maverick local-repo" + gold_processor = BaseTest.expand_environ + outputMatchPrepare = lambda _, s: s.replace("gpg: gpg-agent is not available in this session\n", "") + + def check(self): + super(PublishRepo26Test, self).check() + + # verify signatures + self.run_cmd(["gpg", "--no-auto-check-trustdb", "--keyring", os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files", "aptly_passphrase.pub"), + "--verify", os.path.join(os.environ["HOME"], ".aptly", 'public/dists/maverick/InRelease')]) + self.run_cmd(["gpg", "--no-auto-check-trustdb", "--keyring", os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files", "aptly_passphrase.pub"), + "--verify", os.path.join(os.environ["HOME"], ".aptly", 'public/dists/maverick/Release.gpg'), + os.path.join(os.environ["HOME"], ".aptly", 'public/dists/maverick/Release')]) + + +class PublishRepo27Test(BaseTest): + """ + publish repo: with udebs + """ + fixtureCmds = [ + "aptly repo create local-repo", + "aptly repo add local-repo ${files} ${udebs}", + ] + runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick local-repo" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishRepo27Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Release') + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Release') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + self.check_exists('public/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb') + self.check_exists('public/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb') + + # verify contents except of sums + self.check_file_contents('public/dists/maverick/main/debian-installer/binary-i386/Packages', 'udeb_binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) diff --git a/src/github.com/smira/aptly/system/t06_publish/s3.py b/src/github.com/smira/aptly/system/t06_publish/s3.py new file mode 100644 index 00000000..cdb202c9 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/s3.py @@ -0,0 +1,158 @@ +from s3_lib import S3Test + + +def strip_processor(output): + return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:')]) + + +class S3Publish1Test(S3Test): + """ + publish to S3: from repo + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${files}", + ] + runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo s3:test1:" + + def check(self): + super(S3Publish1Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + # # verify contents except of sums + self.check_file_contents('public/dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('public/dists/maverick/main/source/Sources', 'sources', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + self.check_file_contents('public/dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class S3Publish2Test(S3Test): + """ + publish to S3: publish update removed some packages + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${files}/", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo s3:test1:", + "aptly repo remove local-repo pyspi" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick s3:test1:" + + def check(self): + super(S3Publish2Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + # verify contents except of sums + self.check_file_contents('public/dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('public/dists/maverick/main/source/Sources', 'sources', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + self.check_file_contents('public/dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class S3Publish3Test(S3Test): + """ + publish to S3: publish switch - removed some packages + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot pull -no-deps -architectures=i386,amd64 snap2 snap1 snap3 gnuplot-x11", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick snap1 s3:test1:", + ] + runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick s3:test1: snap3" + + def check(self): + super(S3Publish3Test, self).check() + + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + + self.check_exists('public/pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb') + self.check_exists('public/pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb') + self.check_not_exists('public/pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb') + self.check_not_exists('public/pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb') + + # verify contents except of sums + self.check_file_contents('public/dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('public/dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class S3Publish4Test(S3Test): + """ + publish to S3: multiple repos, list + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${udebs}", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo s3:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=xyz local-repo s3:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo s3:test1:prefix", + ] + runCmd = "aptly publish list" + + +class S3Publish5Test(S3Test): + """ + publish to S3: publish drop - component cleanup + """ + fixtureCmds = [ + "aptly repo create local1", + "aptly repo create local2", + "aptly repo add local1 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb", + "aptly repo add local2 ${files}", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq1 local1 s3:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq2 local2 s3:test1:", + ] + runCmd = "aptly publish drop sq2 s3:test1:" + + def check(self): + super(S3Publish5Test, self).check() + + self.check_exists('public/dists/sq1') + self.check_not_exists('public/dists/sq2') + self.check_exists('public/pool/main/') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') diff --git a/src/github.com/smira/aptly/system/t06_publish/snapshot.py b/src/github.com/smira/aptly/system/t06_publish/snapshot.py index 10d26b36..5fac3d25 100644 --- a/src/github.com/smira/aptly/system/t06_publish/snapshot.py +++ b/src/github.com/smira/aptly/system/t06_publish/snapshot.py @@ -8,6 +8,10 @@ def strip_processor(output): return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:')]) +def sorted_processor(output): + return "\n".join(sorted(output.split("\n"))) + + class PublishSnapshot1Test(BaseTest): """ publish snapshot: defaults @@ -27,12 +31,16 @@ class PublishSnapshot1Test(BaseTest): self.check_exists('public/dists/maverick/Release') self.check_exists('public/dists/maverick/Release.gpg') + self.check_exists('public/dists/maverick/main/binary-i386/Release') self.check_exists('public/dists/maverick/main/binary-i386/Packages') self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/binary-amd64/Release') self.check_exists('public/dists/maverick/main/binary-amd64/Packages') self.check_exists('public/dists/maverick/main/binary-amd64/Packages.gz') self.check_exists('public/dists/maverick/main/binary-amd64/Packages.bz2') + self.check_not_exists('public/dists/maverick/main/debian-installer/binary-i386/Packages') + self.check_not_exists('public/dists/maverick/main/debian-installer/binary-amd64/Packages') self.check_exists('public/pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb') @@ -42,6 +50,9 @@ class PublishSnapshot1Test(BaseTest): self.check_file_contents('public/dists/maverick/main/binary-i386/Release', 'release_i386') self.check_file_contents('public/dists/maverick/main/binary-amd64/Release', 'release_amd64') + self.check_file_contents('public/dists/maverick/main/binary-i386/Packages', 'packages_i386', match_prepare=sorted_processor) + self.check_file_contents('public/dists/maverick/main/binary-amd64/Packages', 'packages_amd64', match_prepare=sorted_processor) + # verify signatures self.run_cmd(["gpg", "--no-auto-check-trustdb", "--keyring", os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files", "aptly.pub"), "--verify", os.path.join(os.environ["HOME"], ".aptly", 'public/dists/maverick/InRelease')]) @@ -822,3 +833,96 @@ class PublishSnapshot34Test(BaseTest): super(PublishSnapshot34Test, self).check() self.check_file_contents("public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz", "file") + + +class PublishSnapshot35Test(BaseTest): + """ + publish snapshot: mirror with udebs + """ + fixtureGpg = True + fixtureCmds = [ + "aptly -architectures=i386,amd64 mirror create -keyring=aptlytest.gpg -filter='$$Source (dmraid)' -with-udebs squeeze http://mirror.yandex.ru/debian/ squeeze main non-free", + "aptly mirror update -keyring=aptlytest.gpg squeeze", + "aptly snapshot create squeeze from mirror squeeze", + ] + runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec squeeze" + gold_processor = BaseTest.expand_environ + + def check(self): + super(PublishSnapshot35Test, self).check() + + self.check_exists('public/dists/squeeze/InRelease') + self.check_exists('public/dists/squeeze/Release') + self.check_exists('public/dists/squeeze/Release.gpg') + + self.check_exists('public/dists/squeeze/main/binary-i386/Release') + self.check_exists('public/dists/squeeze/main/binary-i386/Packages') + self.check_exists('public/dists/squeeze/main/binary-i386/Packages.gz') + self.check_exists('public/dists/squeeze/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-i386/Release') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-i386/Packages') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-i386/Packages.gz') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-i386/Packages.bz2') + self.check_exists('public/dists/squeeze/main/binary-amd64/Release') + self.check_exists('public/dists/squeeze/main/binary-amd64/Packages') + self.check_exists('public/dists/squeeze/main/binary-amd64/Packages.gz') + self.check_exists('public/dists/squeeze/main/binary-amd64/Packages.bz2') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-amd64/Release') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-amd64/Packages') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-amd64/Packages.gz') + self.check_exists('public/dists/squeeze/main/debian-installer/binary-amd64/Packages.bz2') + self.check_not_exists('public/dists/squeeze/main/source/Sources') + self.check_not_exists('public/dists/squeeze/main/source/Sources.gz') + self.check_not_exists('public/dists/squeeze/main/source/Sources.bz2') + + self.check_exists('public/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb') + self.check_exists('public/pool/main/d/dmraid/dmraid-udeb_1.0.0.rc16-4.1_i386.udeb') + self.check_exists('public/pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_amd64.deb') + self.check_exists('public/pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_i386.deb') + + self.check_file_contents('public/dists/squeeze/main/binary-i386/Packages', 'packages_i386', match_prepare=sorted_processor) + self.check_file_contents('public/dists/squeeze/main/debian-installer/binary-i386/Packages', 'packages_udeb_i386', match_prepare=sorted_processor) + self.check_file_contents('public/dists/squeeze/main/binary-amd64/Packages', 'packages_amd64', match_prepare=sorted_processor) + self.check_file_contents('public/dists/squeeze/main/debian-installer/binary-amd64/Packages', 'packages_udeb_amd64', match_prepare=sorted_processor) + + # verify contents except of sums + self.check_file_contents('public/dists/squeeze/Release', 'release', match_prepare=strip_processor) + + self.check_file_contents('public/dists/squeeze/main/debian-installer/binary-i386/Release', 'release_udeb_i386', match_prepare=strip_processor) + + # verify sums + release = self.read_file('public/dists/squeeze/Release').split("\n") + release = [l for l in release if l.startswith(" ")] + pathsSeen = set() + for l in release: + fileHash, fileSize, path = l.split() + pathsSeen.add(path) + + fileSize = int(fileSize) + + st = os.stat(os.path.join(os.environ["HOME"], ".aptly", 'public/dists/squeeze/', path)) + if fileSize != st.st_size: + raise Exception("file size doesn't match for %s: %d != %d" % (path, fileSize, st.st_size)) + + if len(fileHash) == 32: + h = hashlib.md5() + elif len(fileHash) == 40: + h = hashlib.sha1() + else: + h = hashlib.sha256() + + h.update(self.read_file(os.path.join('public/dists/squeeze', path))) + + if h.hexdigest() != fileHash: + raise Exception("file hash doesn't match for %s: %s != %s" % (path, fileHash, h.hexdigest())) + + pathsExepcted = set() + for arch in ("i386", "amd64"): + for udeb in ("", "debian-installer/"): + for ext in ("", ".gz", ".bz2"): + pathsExepcted.add("main/%sbinary-%s/Packages%s" % (udeb, arch, ext)) + + pathsExepcted.add("main/%sbinary-%s/Release" % (udeb, arch)) + + if pathsSeen != pathsExepcted: + raise Exception("path seen wrong: %r != %r" % (pathsSeen, pathsExepcted)) diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1-1.3.conflict.dsc b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1-1.3.conflict.dsc new file mode 100644 index 00000000..21a67a86 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1-1.3.conflict.dsc @@ -0,0 +1,12 @@ +Format: 1.0 +Source: pyspi +Binary: python-at-spi +Architecture: any +Version: 0.6.1-1.3 +Maintainer: Jose Carlos Garcia Sogo +Homepage: http://people.redhat.com/zcerza/dogtail +Standards-Version: 3.7.3 +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Files: + d41d8cd98f00b204e9800998ecf8427e 0 pyspi_0.6.1.orig.tar.gz diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1.orig.tar.gz b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test/pyspi_0.6.1.orig.tar.gz new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_gold b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_gold new file mode 100644 index 00000000..ea24cee5 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_gold @@ -0,0 +1,3 @@ +Loading packages... +[-] pyspi_0.6.1-1.3_source removed due to conflict with package being added +[+] pyspi_0.6.1-1.3_source added diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_repo_show b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_repo_show new file mode 100644 index 00000000..28d1e716 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo11Test_repo_show @@ -0,0 +1,7 @@ +Name: repo11 +Comment: Repo11 +Default Distribution: squeeze +Default Component: main +Number of packages: 1 +Packages: + pyspi_0.6.1-1.3_source diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_gold b/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_gold new file mode 100644 index 00000000..362cf9ac --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_gold @@ -0,0 +1,2 @@ +Loading packages... +[+] dmraid-udeb_1.0.0.rc16-4.1_amd64 added diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_repo_show b/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_repo_show new file mode 100644 index 00000000..b5cda2da --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo12Test_repo_show @@ -0,0 +1,7 @@ +Name: repo12 +Comment: Repo12 +Default Distribution: squeeze +Default Component: main +Number of packages: 1 +Packages: + dmraid-udeb_1.0.0.rc16-4.1_amd64 diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_gold b/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_gold new file mode 100644 index 00000000..89051da0 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_gold @@ -0,0 +1,6 @@ +Loading packages... +[+] libboost-program-options-dev_1.49.0.1_i386 added +[+] pyspi_0.6.1-1.4_source added +[+] pyspi_0.6.1-1.3_source added +[+] dmraid-udeb_1.0.0.rc16-4.1_amd64 added +[+] dmraid-udeb_1.0.0.rc16-4.1_i386 added diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_repo_show b/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_repo_show new file mode 100644 index 00000000..b921958a --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo13Test_repo_show @@ -0,0 +1,11 @@ +Name: repo13 +Comment: Repo13 +Default Distribution: squeeze +Default Component: main +Number of packages: 5 +Packages: + dmraid-udeb_1.0.0.rc16-4.1_amd64 + dmraid-udeb_1.0.0.rc16-4.1_i386 + libboost-program-options-dev_1.49.0.1_i386 + pyspi_0.6.1-1.3_source + pyspi_0.6.1-1.4_source diff --git a/src/github.com/smira/aptly/system/t09_repo/SearchRepo1Test_gold b/src/github.com/smira/aptly/system/t09_repo/SearchRepo1Test_gold new file mode 100644 index 00000000..52405682 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/SearchRepo1Test_gold @@ -0,0 +1,4043 @@ + +aolserver4-dev_4.5.1-15.1_i386 +apache2-prefork-dev_2.2.22-13+deb7u1_i386 +apache2-threaded-dev_2.2.22-13+deb7u1_i386 +apcalc-dev_2.12.4.4-3_i386 +aplus-fsf-dev_4.22.1-6_i386 +aroarfw-dev_0.1~beta4-5_all +asterisk-dev_1:1.8.13.1~dfsg1-3+deb7u3_all +atfs-dev_1.4pl6-11_i386 +audacious-dev_3.2.4-1_i386 +autotools-dev_20120608.1_all +binutils-dev_2.22-8_i386 +biosquid-dev_1.9g+cvs20050121-2_i386 +bitlbee-dev_3.0.5-1.2_all +blacs-pvm-dev_1.1-21_i386 +blends-dev_0.6.16.2_all +blktap-dev_2.0.90-1_i386 +blt-dev_2.4z-4.2_i386 +boinc-dev_7.0.27+dfsg-5_i386 +boolstuff-dev_0.1.12-3_i386 +cairo-dock-dev_3.0.0-2+deb7u1_i386 +cernlib-base-dev_20061220+dfsg3-2_all +cernlib-core-dev_20061220+dfsg3-2_all +chipmunk-dev_5.3.4-1_i386 +cimg-dev_1.4.9-2_all +clearsilver-dev_0.10.5-1.3_i386 +cli-common-dev_0.8.2_all +clinica-dev_0.2.1~dfsg-1_i386 +clisp-dev_1:2.49-8.1_i386 +cluster-glue-dev_1.0.9+hg2665-1_i386 +codeblocks-dev_10.05-2.1_i386 +coinor-libcbc-dev_2.5.0-3_i386 +coinor-libcgl-dev_0.55.0-1.1_i386 +coinor-libclp-dev_1.12.0-2.1_i386 +coinor-libcoinutils-dev_2.6.4-3_i386 +coinor-libdylp-dev_1.6.0-1.1_i386 +coinor-libflopc++-dev_1.0.6-3.1_i386 +coinor-libipopt-dev_3.10.2-1.1_i386 +coinor-libosi-dev_0.103.0-1_i386 +coinor-libsymphony-dev_5.2.4-1.2_i386 +coinor-libvol-dev_1.1.7-1_i386 +collectd-dev_5.1.0-3_all +comerr-dev_2.1-1.42.5-1.1_i386 +condor-dev_7.8.2~dfsg.1-1+deb7u1_i386 +config-package-dev_4.13_all +connman-dev_1.0-1.1+wheezy1+b1_i386 +console-tools-dev_1:0.2.3dbs-70_i386 +coop-computing-tools-dev_3.5.1-2_i386 +corosync-dev_1.4.2-3_i386 +courier-authlib-dev_0.63.0-6+b1_i386 +crtmpserver-dev_1.0~dfsg-3_i386 +ctapi-dev_1.1_all +ctn-dev_3.0.6-13+b2_i386 +cyrus-dev_2.4.16-4+deb7u1_all +dcap-dev_2.47.6-2_i386 +dico-dev_2.1-3+b2_i386 +dictionaries-common-dev_1.12.11_all +dietlibc-dev_0.33~cvs20120325-4_i386 +dolfin-dev_1.0.0-7_all +dovecot-dev_1:2.1.7-7_i386 +dpkg-dev_1.16.12_all +drac-dev_1.12-7.2_i386 +drizzle-plugin-dev_1:7.1.36-stable-1_i386 +dssi-dev_1.1.1~dfsg0-1_all +e2fslibs-dev_1.42.5-1.1_i386 +emerillon-dev_0.1.90-1_i386 +eog-dev_3.4.2-1+build1_all +epiphany-browser-dev_3.4.2-2.1_i386 +erlang-dev_1:15.b.1-dfsg-4+deb7u1_i386 +erlang-esdl-dev_1.2-2_all +etl-dev_0.04.15-1_i386 +evolution-data-server-dev_3.4.4-3_i386 +evolution-dev_3.4.4-3_i386 +exim4-dev_4.80-7_i386 +expect-dev_5.45-2_i386 +expeyes-firmware-dev_2.0.0-3_all +extremetuxracer-gimp-dev_0.4-5_all +falconpl-dev_0.9.6.9-git20120606-2_i386 +fatrat-dev_1.1.3-5_all +fcitx-libs-dev_1:4.2.4.1-7_i386 +fenix-dev_0.92a.dfsg1-9_all +festival-dev_1:2.1~release-5.1_i386 +fftw-dev_2.1.5-1_i386 +finch-dev_2.10.9-1~deb7u1_all +firebird-dev_2.5.2.26540.ds4-1~deb7u1_i386 +flite1-dev_1.4-release-6_i386 +flow-tools-dev_1:0.68-12.1+b1_i386 +fosfat-dev_0.4.0-3_i386 +freeglut3-dev_2.6.0-4_i386 +freetds-dev_0.91-2+deb7u1_i386 +frei0r-plugins-dev_1.1.22git20091109-1.2_i386 +ftgl-dev_2.1.3~rc5-4_all +ftplib-dev_3.1-1-9_i386 +gambas3-dev_3.1.1-2+b1_i386 +gap-dev_4r4p12-2_i386 +gauche-dev_0.9.1-5.1_i386 +gcc-4.6-plugin-dev_4.6.3-14_i386 +gcc-4.7-plugin-dev_4.7.2-5_i386 +gcin-dev_2.7.6.1+dfsg-1_all +gedit-dev_3.4.2-1_all +gem-dev_1:0.93.3-5_all +genius-dev_1.0.14-1_i386 +gfxboot-dev_4.5.0-3_i386 +giblib-dev_1.2.4-8_i386 +glabels-dev_3.0.0-3+b1_i386 +glee-dev_5.4.0-1_i386 +gmpc-dev_11.8.16-6_i386 +gnash-dev_0.8.11~git20120629-1+deb7u1_i386 +gnome-control-center-dev_1:3.4.3.1-2_all +gnome-settings-daemon-dev_3.4.2+git20121218.7c1322-3+deb7u3_i386 +gnome-video-effects-dev_0.4.0-1_all +gnumach-dev_2:1.3.99.dfsg.git20120610-1_i386 +gnunet-dev_0.9.3-7_i386 +gnunet-gtk-dev_0.9.3-1_i386 +gnuradio-dev_3.5.3.2-1_i386 +gosa-dev_2.7.4-4.3~deb7u1_all +gpe-ownerinfo-dev_0.28-3_i386 +gpsim-dev_0.26.1-2.1_i386 +graphviz-dev_2.26.3-14+deb7u1_all +grass-dev_6.4.2-2_i386 +gridengine-drmaa-dev_6.2u5-7.1_i386 +gromacs-dev_4.5.5-2_i386 +gsettings-desktop-schemas-dev_3.4.2-3_i386 +gthumb-dev_3:3.0.1-2_i386 +guile-1.6-dev_1.6.8-10.3_i386 +guile-1.8-dev_1.8.8+1-8_i386 +guile-2.0-dev_2.0.5+1-3_i386 +guile-cairo-dev_1.4.0-3_i386 +heartbeat-dev_1:3.0.5-3_i386 +heimdal-dev_1.6~git20120403+dfsg1-2_i386 +hime-dev_0.9.9+git20120619+dfsg-1_all +hybrid-dev_1:7.2.2.dfsg.2-10_all +icedove-dev_10.0.12-1_i386 +inn2-dev_2.5.3-3_i386 +inventor-dev_2.1.5-10-16_i386 +iproute-dev_20120521-3+b3_i386 +iptables-dev_1.4.14-3.1_i386 +irssi-dev_0.8.15-5_i386 +isc-dhcp-dev_4.2.2.dfsg.1-5+deb70u6_i386 +itcl3-dev_3.4.1-1_i386 +itk3-dev_3.3-4_i386 +ivtools-dev_1.2.10a1-1_i386 +kadu-dev_0.11.2-1_all +kannel-dev_1.4.3-2+b2_i386 +kde-workspace-dev_4:4.8.4-6_i386 +kdebase-workspace-dev_4:4.8.4-6_all +kdelibs5-dev_4:4.8.4-4_i386 +kdemultimedia-dev_4:4.8.4-2_i386 +kdepimlibs5-dev_4:4.8.4-2_i386 +kdevelop-dev_4:4.3.1-3+b1_i386 +kdevplatform-dev_1.3.1-2_i386 +kmymoney-dev_4.6.2-3.2_i386 +konwert-dev_1.8-11.2_all +lam4-dev_7.1.4-3_i386 +lesstif2-dev_1:0.95.2-1.1_i386 +lib3ds-dev_1.3.0-6_i386 +lib4store-dev_1.1.4-2_i386 +lib64bz2-dev_1.0.6-4_i386 +lib64expat1-dev_2.1.0-1+deb7u1_i386 +lib64ffi-dev_3.0.10-3_i386 +lib64ncurses5-dev_5.9-10_i386 +lib64readline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +lib64readline6-dev_6.2+dfsg-0.1_i386 +lib64z1-dev_1:1.2.7.dfsg-13_i386 +liba52-0.7.4-dev_0.7.4-16_i386 +libaa1-dev_1.4p5-40_i386 +libaac-tactics-ocaml-dev_0.2.pl2-7_i386 +libaacs-dev_0.4.0-1_i386 +libaal-dev_1.0.5-5.1_i386 +libabiword-2.9-dev_2.9.2+svn20120603-8_i386 +libaccountsservice-dev_0.6.21-8_i386 +libace-dev_6.0.3+dfsg-0.1_i386 +libace-flreactor-dev_6.0.3+dfsg-0.1_i386 +libace-foxreactor-dev_6.0.3+dfsg-0.1_i386 +libace-htbp-dev_6.0.3+dfsg-0.1_i386 +libace-inet-dev_6.0.3+dfsg-0.1_i386 +libace-inet-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-qtreactor-dev_6.0.3+dfsg-0.1_i386 +libace-rmcast-dev_6.0.3+dfsg-0.1_i386 +libace-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-tkreactor-dev_6.0.3+dfsg-0.1_i386 +libace-tmcast-dev_6.0.3+dfsg-0.1_i386 +libace-xtreactor-dev_6.0.3+dfsg-0.1_i386 +libacexml-dev_6.0.3+dfsg-0.1_i386 +libacl1-dev_2.2.51-8_i386 +libacpi-dev_0.2-4_i386 +libacr38ucontrol-dev_1.7.11-1_i386 +libadasockets4-dev_1.8.10-2_i386 +libaddresses-dev_0.4.7-1+b5_i386 +libaddressview-dev_0.4.7-1+b5_i386 +libadios-dev_1.3-11_i386 +libadminutil-dev_1.1.15-1_i386 +libadns1-dev_1.4-2_i386 +libadolc-dev_2.3.0-1_i386 +libadplug-dev_2.2.1+dfsg3-0.1_i386 +libafflib-dev_3.6.6-1.1+b1_i386 +libafrodite-0.12-dev_0.12.1-3_i386 +libafterimage-dev_2.2.11-7_i386 +libagg-dev_2.5+dfsg1-8_i386 +libagrep-ocaml-dev_1.0-11+b3_i386 +libahven3-dev_2.1-4_i386 +libaiksaurus-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaiksaurusgtk-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaio-dev_0.3.109-3_i386 +libakonadi-dev_1.7.2-3_i386 +libalberta2-dev_2.0.1-5_i386 +libaldmb1-dev_1:0.9.3-5.4_i386 +libalglib-dev_2.6.0-6_i386 +libalkimia-dev_4.3.2-1.1_i386 +liballeggl4-dev_2:4.4.2-2.1_i386 +liballegro4.2-dev_2:4.4.2-2.1_i386 +libalog0.4.1-base-dev_0.4.1-2_i386 +libalog0.4.1-full-dev_0.4.1-2_i386 +libalsa-ocaml-dev_0.2.1-1+b1_i386 +libalsaplayer-dev_0.99.80-5.1_i386 +libalure-dev_1.2-6_i386 +libalut-dev_1.1.0-3_i386 +libampsharp-cil-dev_2.0.4-2_all +libamu-dev_6.2+rc20110530-3_i386 +libanalitza-dev_4:4.8.4-2_i386 +libanet0.1-dev_0.1-3_i386 +libanjuta-dev_2:3.4.3-1_i386 +libann-dev_1.1.2+doc-3_i386 +libanthy-dev_9100h-16_i386 +libantlr-dev_2.7.7+dfsg-4_i386 +libantlr3c-dev_3.2-2_i386 +libao-dev_1.1.0-2_i386 +libao-ocaml-dev_0.2.0-1+b2_i386 +libaosd-dev_0.2.7-1_i386 +libapache2-mod-perl2-dev_2.0.7-3_all +libapertium3-3.1-0-dev_3.1.0-2_i386 +libapm-dev_3.2.2-14_i386 +libapol-dev_3.3.7-3_i386 +libapparmor-dev_2.7.103-4_i386 +libappindicator-dev_0.4.92-2_i386 +libappindicator0.1-cil-dev_0.4.92-2_all +libappindicator3-dev_0.4.92-2_i386 +libapq-postgresql3.2.0-dev_3.2.0-2_i386 +libapq3.2.0-dev_3.2.0-1_i386 +libapr-memcache-dev_0.7.0-1_i386 +libapr1-dev_1.4.6-3+deb7u1_i386 +libapreq2-dev_2.13-1+b2_i386 +libapron-dev_0.9.10-5.2_all +libapron-ocaml-dev_0.9.10-5.2+b3_i386 +libaprutil1-dev_1.4.1-3_i386 +libapt-pkg-dev_0.9.7.9+deb7u1_i386 +libaqbanking34-dev_5.0.24-3_i386 +libaqsis-dev_1.8.1-3_i386 +libarchive-dev_3.0.4-3+nmu1_i386 +libargtable2-dev_12-1_i386 +libarmadillo-dev_1:3.2.3+dfsg-1_i386 +libarpack++2-dev_2.3-2_i386 +libarpack2-dev_3.1.1-2.1_i386 +libart-2.0-dev_2.3.21-2_i386 +libart2.0-cil-dev_2.24.2-3_all +libasio-dev_1.4.1-3.2_all +libasis2010-dev_2010-5_i386 +libasm-dev_0.152-1+wheezy1_i386 +libasound2-dev_1.0.25-4_i386 +libaspell-dev_0.60.7~20110707-1_i386 +libass-dev_0.10.0-3_i386 +libassa3.5-5-dev_3.5.1-2_i386 +libassimp-dev_3.0~dfsg-1_i386 +libassuan-dev_2.0.3-1_i386 +libast2-dev_0.7-6+b1_i386 +libasyncns-dev_0.8-4_i386 +libatasmart-dev_0.19-1_i386 +libatd-ocaml-dev_1.0.1-1+b1_i386 +libatdgen-ocaml-dev_1.2.2-1+b1_i386 +libatk-bridge2.0-dev_2.5.3-2_i386 +libatk1.0-dev_2.4.0-2_i386 +libatkmm-1.6-dev_2.22.6-1_i386 +libatlas-base-dev_3.8.4-9+deb7u1_i386 +libatlas-cpp-0.6-dev_0.6.2-3_i386 +libatlas-dev_3.8.4-9+deb7u1_all +libatm1-dev_1:2.5.1-1.5_i386 +libatomic-ops-dev_7.2~alpha5+cvs20101124-1+deb7u1_i386 +libatomicparsley-dev_2.1.2-1_i386 +libatspi-dev_1.32.0-2_i386 +libatspi2.0-dev_2.5.3-2_i386 +libattica-dev_0.2.0-1_i386 +libattr1-dev_1:2.4.46-8_i386 +libaubio-dev_0.3.2-4.2+b1_i386 +libaudio-dev_1.9.3-5wheezy1_i386 +libaudiofile-dev_0.3.4-2_i386 +libaudiomask-dev_1.0-2_i386 +libaudit-dev_1:1.7.18-1.1_i386 +libaugeas-dev_0.10.0-1_i386 +libaunit2-dev_1.03-7_i386 +libautotrace-dev_0.31.1-16+b1_i386 +libautounit-dev_0.20.1-4_i386 +libavahi-cil-dev_0.6.19-4.2_all +libavahi-client-dev_0.6.31-2_i386 +libavahi-common-dev_0.6.31-2_i386 +libavahi-compat-libdnssd-dev_0.6.31-2_i386 +libavahi-core-dev_0.6.31-2_i386 +libavahi-glib-dev_0.6.31-2_i386 +libavahi-gobject-dev_0.6.31-2_i386 +libavahi-qt4-dev_0.6.31-2_i386 +libavahi-ui-cil-dev_0.6.19-4.2_all +libavahi-ui-dev_0.6.31-2_i386 +libavahi-ui-gtk3-dev_0.6.31-2_i386 +libavbin-dev_7-1.3_i386 +libavc1394-dev_0.5.4-2_i386 +libavcodec-dev_6:0.8.10-1_i386 +libavdevice-dev_6:0.8.10-1_i386 +libavfilter-dev_6:0.8.10-1_i386 +libavformat-dev_6:0.8.10-1_i386 +libavifile-0.7-dev_1:0.7.48~20090503.ds-13_i386 +libavl-dev_0.3.5-3_i386 +libavogadro-dev_1.0.3-5_i386 +libavutil-dev_6:0.8.10-1_i386 +libaws2.10.2-dev_2.10.2-4_i386 +libax25-dev_0.0.12-rc2+cvs20120204-2_i386 +libbabl-dev_0.1.10-1_i386 +libball1.4-dev_1.4.1+20111206-4_i386 +libballview1.4-dev_1.4.1+20111206-4_i386 +libbam-dev_0.1.18-1_i386 +libbamf-dev_0.2.118-1_i386 +libbamf3-dev_0.2.118-1_i386 +libbarry-dev_0.18.3-5_i386 +libbatteries-ocaml-dev_1.4.3-1_i386 +libbdd-dev_2.4-8_i386 +libbeecrypt-dev_4.2.1-4_i386 +libbenchmark-ocaml-dev_0.9-2+b3_i386 +libbfb0-dev_0.23-1.1_i386 +libbg1-dev_1.106-1_i386 +libbibutils-dev_4.12-5_i386 +libbin-prot-camlp4-dev_2.0.7-1_i386 +libbind-dev_1:9.8.4.dfsg.P1-6+nmu2+deb7u1_i386 +libbind4-dev_6.0-1_i386 +libbinio-dev_1.4+dfsg1-1_i386 +libbiniou-ocaml-dev_1.0.0-1+b1_i386 +libbio2jack0-dev_0.9-2.1_i386 +libbiococoa-dev_2.2.2-1+b2_i386 +libbiosig-dev_1.3.0-2_i386 +libbisho-common-dev_0.27.2+git20111122.9e68ef3d-1_i386 +libbison-dev_1:2.5.dfsg-2.1_i386 +libbitmask-dev_2.0-2_i386 +libbitstream-dev_1.0-1_all +libbitstring-ocaml-dev_2.0.2-3+b1_i386 +libbjack-ocaml-dev_0.1.3-5+b1_i386 +libblacs-mpi-dev_1.1-31_i386 +libblas-dev_1.2.20110419-5_i386 +libbliss-dev_0.72-4_i386 +libblitz0-dev_1:0.9-13_i386 +libblkid-dev_2.20.1-5.3_i386 +libblocksruntime-dev_0.1-1_i386 +libbluedevil-dev_1.9.2-1_i386 +libbluetooth-dev_4.99-2_i386 +libbluray-dev_1:0.2.2-1_i386 +libbml-dev_0.6.1-1_i386 +libbobcat-dev_3.01.00-1+b1_i386 +libbogl-dev_0.1.18-8+b1_i386 +libbognor-regis-dev_0.6.12+git20101007.02c25268-7_i386 +libbonobo2-dev_2.24.3-1_i386 +libbonoboui2-dev_2.24.3-1_i386 +libboo-cil-dev_0.9.5~git20110729.r1.202a430-2_all +libboost-all-dev_1.49.0.1_i386 +libboost-chrono-dev_1.49.0.1_i386 +libboost-chrono1.49-dev_1.49.0-3.2_i386 +libboost-date-time-dev_1.49.0.1_i386 +libboost-date-time1.49-dev_1.49.0-3.2_i386 +libboost-dev_1.49.0.1_i386 +libboost-filesystem-dev_1.49.0.1_i386 +libboost-filesystem1.49-dev_1.49.0-3.2_i386 +libboost-graph-dev_1.49.0.1_i386 +libboost-graph-parallel-dev_1.49.0.1_i386 +libboost-graph-parallel1.49-dev_1.49.0-3.2_i386 +libboost-graph1.49-dev_1.49.0-3.2_i386 +libboost-iostreams-dev_1.49.0.1_i386 +libboost-iostreams1.49-dev_1.49.0-3.2_i386 +libboost-locale-dev_1.49.0.1_i386 +libboost-locale1.49-dev_1.49.0-3.2_i386 +libboost-math-dev_1.49.0.1_i386 +libboost-math1.49-dev_1.49.0-3.2_i386 +libboost-mpi-dev_1.49.0.1_i386 +libboost-mpi-python-dev_1.49.0.1_i386 +libboost-mpi-python1.49-dev_1.49.0-3.2_i386 +libboost-mpi1.49-dev_1.49.0-3.2_i386 +libboost-program-options-dev_1.49.0.1_i386 +libboost-program-options1.49-dev_1.49.0-3.2_i386 +libboost-python-dev_1.49.0.1_i386 +libboost-python1.49-dev_1.49.0-3.2_i386 +libboost-random-dev_1.49.0.1_i386 +libboost-random1.49-dev_1.49.0-3.2_i386 +libboost-regex-dev_1.49.0.1_i386 +libboost-regex1.49-dev_1.49.0-3.2_i386 +libboost-serialization-dev_1.49.0.1_i386 +libboost-serialization1.49-dev_1.49.0-3.2_i386 +libboost-signals-dev_1.49.0.1_i386 +libboost-signals1.49-dev_1.49.0-3.2_i386 +libboost-system-dev_1.49.0.1_i386 +libboost-system1.49-dev_1.49.0-3.2_i386 +libboost-test-dev_1.49.0.1_i386 +libboost-test1.49-dev_1.49.0-3.2_i386 +libboost-thread-dev_1.49.0.1_i386 +libboost-thread1.49-dev_1.49.0-3.2_i386 +libboost-timer-dev_1.49.0.1_i386 +libboost-timer1.49-dev_1.49.0-3.2_i386 +libboost-wave-dev_1.49.0.1_i386 +libboost-wave1.49-dev_1.49.0-3.2_i386 +libboost1.49-all-dev_1.49.0-3.2_i386 +libboost1.49-dev_1.49.0-3.2_i386 +libbotan1.10-dev_1.10.5-1_i386 +libbox-dev_2.5-2_i386 +libbox2d-dev_2.0.1+dfsg1-1_i386 +libbpp-core-dev_2.0.3-1_i386 +libbpp-phyl-dev_2.0.3-1_i386 +libbpp-popgen-dev_2.0.3-1_i386 +libbpp-qt-dev_2.0.2-1_i386 +libbpp-raa-dev_2.0.3-1_i386 +libbpp-seq-dev_2.0.3-1_i386 +libbrahe-dev_1.3.2-3_i386 +libbrasero-media3-dev_3.4.1-4_i386 +libbrlapi-dev_4.4-10+deb7u1_i386 +libbs2b-dev_3.1.0+dfsg-2_i386 +libbsd-dev_0.4.2-1_i386 +libbse-dev_0.7.4-5_all +libbt-dev_0.70.1-13_i386 +libbtparse-dev_0.63-1_i386 +libbuffy-dev_1.7-1_i386 +libbulletml-dev_0.0.6-5_i386 +libburn-dev_1.2.2-2_i386 +libbuzztard-dev_0.5.0-4_i386 +libbz2-dev_1.0.6-4_i386 +libbz2-ocaml-dev_0.6.0-6+b2_i386 +libc-ares-dev_1.9.1-3_i386 +libc-client2007e-dev_8:2007f~dfsg-2_i386 +libc6-dev_2.13-38+deb7u1_i386 +libcableswig-dev_0.1.0+cvs20111009-1_i386 +libcaca-dev_0.99.beta18-1_i386 +libcairo-ocaml-dev_1:1.2.0-2+b1_i386 +libcairo2-dev_1.12.2-3_i386 +libcairomm-1.0-dev_1.10.0-1_i386 +libcal3d12-dev_0.11.0-4.1_i386 +libcalendar-ocaml-dev_2.03-1+b2_i386 +libcamel1.2-dev_3.4.4-3_i386 +libcameleon-ocaml-dev_1.9.21-2+b1_i386 +libcaml2html-ocaml-dev_1.4.1-3_i386 +libcamlimages-ocaml-dev_1:4.0.1-4+b2_i386 +libcamljava-ocaml-dev_0.3-1+b3_i386 +libcamltemplate-ocaml-dev_1.0.2-1+b2_i386 +libcamomile-ocaml-dev_0.8.4-2_i386 +libcanberra-dev_0.28-6_i386 +libcanberra-gtk-common-dev_0.28-6_all +libcanberra-gtk-dev_0.28-6_i386 +libcanberra-gtk3-dev_0.28-6_i386 +libcanlock2-dev_2b-6_i386 +libcanna1g-dev_3.7p3-11_i386 +libcap-dev_1:2.22-1.2_i386 +libcap-ng-dev_0.6.6-2_i386 +libcapi20-dev_1:3.25+dfsg1-3.3~deb7u1_i386 +libcapsinetwork-dev_0.3.0-7_i386 +libcaribou-dev_0.4.4-1_i386 +libcbf-dev_0.7.9.1-3_i386 +libccaudio2-dev_2.0.5-3_i386 +libccfits-dev_2.4-1_i386 +libcconv-dev_0.6.2-1_i386 +libccrtp-dev_2.0.3-4_i386 +libccs-dev_3.0.12-3.2+deb7u2_i386 +libccscript3-dev_1.1.7-2_i386 +libccss-dev_0.5.0-4_i386 +libcdaudio-dev_0.99.12p2-12_i386 +libcdb-dev_0.78_i386 +libcdd-dev_094b.dfsg-4.2_i386 +libcddb2-dev_1.3.2-3_i386 +libcdi-dev_1.5.4+dfsg.1-5_i386 +libcdio-cdda-dev_0.83-4_i386 +libcdio-dev_0.83-4_i386 +libcdio-paranoia-dev_0.83-4_i386 +libcdk5-dev_5.0.20060507-4_i386 +libcdparanoia-dev_3.10.2+debian-10.1_i386 +libcec-dev_1.6.2-1.1_i386 +libcegui-mk2-dev_0.7.6-2+b1_i386 +libcext-dev_6.1.1-2_i386 +libcf-ocaml-dev_0.10-3+b3_i386 +libcfg-dev_1.4.2-3_i386 +libcfitsio3-dev_3.300-2_i386 +libcgal-dev_4.0-5_i386 +libcgic-dev_2.05-3_i386 +libcgicc5-dev_3.2.9-3_i386 +libcgns-dev_3.1.3.4-1+b1_i386 +libcgroup-dev_0.38-1_i386 +libcgsi-gsoap-dev_1.3.5-1_i386 +libchamplain-0.12-dev_0.12.3-1_i386 +libchamplain-gtk-0.12-dev_0.12.3-1_i386 +libcharls-dev_1.0-2_i386 +libchasen-dev_2.4.5-6_i386 +libcheese-dev_3.4.2-2_i386 +libcheese-gtk-dev_3.4.2-2_i386 +libchewing3-dev_0.3.3-4_i386 +libchicken-dev_4.7.0-1_i386 +libchipcard-dev_5.0.3beta-3_i386 +libchise-dev_0.3.0-2+b1_i386 +libchm-dev_2:0.40a-2_i386 +libchromaprint-dev_0.6-2_i386 +libcib1-dev_1.1.7-1_i386 +libcitadel-dev_8.14-1_i386 +libcitygml0-dev_0.14+svn128-1+3p0p1+4_i386 +libck-connector-dev_0.4.5-3.1_i386 +libckyapplet1-dev_1.1.0-12_i386 +libclalsadrv-dev_2.0.0-3_all +libclam-dev_1.4.0-5.1_i386 +libclam-qtmonitors-dev_1.4.0-3.1_i386 +libclamav-dev_0.98.1+dfsg-1+deb7u3_i386 +libclang-common-dev_1:3.0-6.2_i386 +libclang-dev_1:3.0-6.2_i386 +libclanlib-dev_1.0~svn3827-3_i386 +libclassad-dev_7.8.2~dfsg.1-1+deb7u1_i386 +libclaw-application-dev_1.7.0-3_i386 +libclaw-configuration-file-dev_1.7.0-3_i386 +libclaw-dev_1.7.0-3_i386 +libclaw-dynamic-library-dev_1.7.0-3_i386 +libclaw-graphic-dev_1.7.0-3_i386 +libclaw-logger-dev_1.7.0-3_i386 +libclaw-net-dev_1.7.0-3_i386 +libclaw-tween-dev_1.7.0-3_i386 +libclaws-mail-dev_3.8.1-2_i386 +libclhep-dev_2.1.2.3-1_i386 +libcli-dev_1.9.6-1_i386 +libclippoly-dev_0.11-3_i386 +libclips-dev_6.24-3_i386 +libcliquer-dev_1.21-1_i386 +libcln-dev_1.3.2-1.2_i386 +libcloog-isl-dev_0.17.0-3_i386 +libcloog-ppl-dev_0.15.11-4_i386 +libclthreads-dev_2.4.0-4_i386 +libclucene-dev_0.9.21b-2+b1_i386 +libclustalo-dev_1.1.0-1_i386 +libcluster-glue-dev_1.0.9+hg2665-1_all +libclutter-1.0-dev_1.10.8-2_i386 +libclutter-cil-dev_1.0.0~alpha3~git20090817.r1.349dba6-8_all +libclutter-gst-dev_1.5.4-1+build0_i386 +libclutter-gtk-1.0-dev_1.2.0-2_i386 +libclutter-imcontext-0.1-dev_0.1.4-3_i386 +libcluttergesture-dev_0.0.2.1-7_i386 +libclxclient-dev_3.6.1-6_i386 +libcman-dev_3.0.12-3.2+deb7u2_i386 +libcminpack-dev_1.2.2-1_i386 +libcmis-dev_0.1.0-1+b1_i386 +libcmor-dev_2.8.0-2+b1_i386 +libcmph-dev_0.9-1_i386 +libcneartree-dev_3.1.1-1_i386 +libcnf-dev_4.0-2_i386 +libcob1-dev_1.1-1_i386 +libcogl-dev_1.10.2-7_i386 +libcogl-pango-dev_1.10.2-7_i386 +libcoin60-dev_3.1.3-2.2_i386 +libcojets2-dev_20061220+dfsg3-2_i386 +libcollectdclient-dev_5.1.0-3_i386 +libcollection-dev_0.1.3-2_i386 +libcolorblind-dev_0.0.1-1_i386 +libcolord-dev_0.1.21-1_i386 +libcolord-gtk-dev_0.1.21-1_i386 +libcolorhug-dev_0.1.10-1_i386 +libcomedi-dev_0.10.0-3_i386 +libcommoncpp2-dev_1.8.1-5_i386 +libcompfaceg1-dev_1:1.5.2-5_i386 +libconcord-dev_0.24-1.1_i386 +libconfdb-dev_1.4.2-3_i386 +libconfig++-dev_1.4.8-5_i386 +libconfig++8-dev_1.4.8-5_i386 +libconfig-dev_1.4.8-5_i386 +libconfig-file-ocaml-dev_1.1-1_i386 +libconfig8-dev_1.4.8-5_i386 +libconfuse-dev_2.7-4_i386 +libcontactsdb-dev_0.5-8_i386 +libcoq-ocaml-dev_8.3.pl4+dfsg-2_i386 +libcore-ocaml-dev_107.01-5_i386 +libcorelinux-dev_0.4.32-7.3_i386 +libcoroipcc-dev_1.4.2-3_i386 +libcoroipcs-dev_1.4.2-3_i386 +libcorosync-dev_1.4.2-3_all +libcos4-dev_4.1.6-2_i386 +libcothreads-ocaml-dev_0.10-3+b3_i386 +libcoyotl-dev_3.1.0-5_i386 +libcpg-dev_1.4.2-3_i386 +libcpl-dev_6.1.1-2_i386 +libcppcutter-dev_1.1.7-1.2_i386 +libcppunit-dev_1.12.1-4_i386 +libcppunit-subunit-dev_0.0.8+bzr176-1_i386 +libcpputest-dev_3.1-2_i386 +libcpufreq-dev_008-1_i386 +libcpuset-dev_1.0-3_i386 +libcqrlib2-dev_1.1.2-1_i386 +libcr-dev_0.8.5-2_i386 +libcrack2-dev_2.8.19-3_i386 +libcreal-ocaml-dev_0.7-6+b3_i386 +libcrmcluster1-dev_1.1.7-1_i386 +libcrmcommon2-dev_1.1.7-1_i386 +libcroco3-dev_0.6.6-2_i386 +libcry-ocaml-dev_0.2.2-1+b1_i386 +libcryptgps-ocaml-dev_0.2.1-7+b3_i386 +libcrypto++-dev_5.6.1-6_i386 +libcryptokit-ocaml-dev_1.5-1_i386 +libcryptsetup-dev_2:1.4.3-4_i386 +libcryptui-dev_3.2.2-1_i386 +libcrystalhd-dev_1:0.0~git20110715.fdd2f19-9_i386 +libcsfml-dev_1.6-1_i386 +libcsnd-dev_1:5.17.11~dfsg-3_all +libcsoap-dev_1.1.0-17.1_i386 +libcsound64-dev_1:5.17.11~dfsg-3_all +libcsoundac-dev_1:5.17.11~dfsg-3_all +libcsv-ocaml-dev_1.2.2-1+b1_i386 +libctapimkt0-dev_1.0.1-1.1_i386 +libctdb-dev_1.12+git20120201-4_i386 +libctemplate-dev_2.2-3_i386 +libctl-dev_3.1.0-5_i386 +libctpl-dev_0.3.3.dfsg-2_i386 +libcuba3-dev_3.0+20111124-2_i386 +libcudf-dev_0.6.2-1_i386 +libcudf-ocaml-dev_0.6.2-1_i386 +libcue-dev_1.4.0-1_i386 +libcunit1-dev_2.1-0.dfsg-10_i386 +libcunit1-ncurses-dev_2.1-0.dfsg-10_i386 +libcups2-dev_1.5.3-5+deb7u1_i386 +libcupscgi1-dev_1.5.3-5+deb7u1_i386 +libcupsdriver1-dev_1.5.3-5+deb7u1_i386 +libcupsfilters-dev_1.0.18-2.1+deb7u1_i386 +libcupsimage2-dev_1.5.3-5+deb7u1_i386 +libcupsmime1-dev_1.5.3-5+deb7u1_i386 +libcupsppdc1-dev_1.5.3-5+deb7u1_i386 +libcupt2-dev_2.5.9_i386 +libcurl-ocaml-dev_0.5.3-2+b1_i386 +libcurl4-gnutls-dev_7.26.0-1+wheezy9_i386 +libcurl4-nss-dev_7.26.0-1+wheezy9_i386 +libcurl4-openssl-dev_7.26.0-1+wheezy9_i386 +libcurses-ocaml-dev_1.0.3-2_i386 +libcutter-dev_1.1.7-1.2_i386 +libcv-dev_2.3.1-11_i386 +libcvaux-dev_2.3.1-11_i386 +libcvc3-dev_2.4.1-4_i386 +libcvector2-dev_1.0.3-1_i386 +libcvm1-dev_0.96-1+b1_i386 +libcw3-dev_3.0.2-1_i386 +libcwidget-dev_0.5.16-3.4_i386 +libcwiid-dev_0.6.00+svn201-3+b1_i386 +libcwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libcxgb3-dev_1.3.1-1_i386 +libcxxtools-dev_2.1.1-1_i386 +libdacs-dev_1.4.27b-2_i386 +libdaemon-dev_0.14-2_i386 +libdancer-xml0-dev_0.8.2.1-3_i386 +libdap-dev_3.11.1-11_i386 +libdapl-dev_2.0.19-1.1_i386 +libdaq-dev_0.6.2-2_i386 +libdar-dev_2.4.5.debian.1-1_i386 +libdatrie-dev_0.2.5-3_i386 +libdawgdic-dev_0.4.3-1_all +libdb++-dev_5.1.6_i386 +libdb-dev_5.1.6_i386 +libdb-java-dev_5.1.6_i386 +libdb-sql-dev_5.1.6_i386 +libdb4o-cil-dev_8.0.184.15484+dfsg-2_all +libdb5.1++-dev_5.1.29-5_i386 +libdb5.1-dev_5.1.29-5_i386 +libdb5.1-java-dev_5.1.29-5_i386 +libdb5.1-sql-dev_5.1.29-5_i386 +libdb5.1-stl-dev_5.1.29-5_i386 +libdballe-dev_5.18-1_i386 +libdballef-dev_5.18-1_i386 +libdbaudiolib0-dev_0.9.8-6.2_i386 +libdbi-dev_0.8.4-6_i386 +libdbus-1-dev_1.6.8-1+deb7u1_i386 +libdbus-c++-dev_0.9.0-6_i386 +libdbus-glib-1-dev_0.100.2-1_i386 +libdbus-glib1.0-cil-dev_0.5.0-4_all +libdbus-ocaml-dev_0.29-1+b3_i386 +libdbus1.0-cil-dev_0.7.0-5_all +libdbusada0.2-dev_0.2-2_i386 +libdbusmenu-glib-dev_0.6.2-1_i386 +libdbusmenu-gtk-dev_0.6.2-1_i386 +libdbusmenu-gtk3-dev_0.6.2-1_i386 +libdbusmenu-jsonloader-dev_0.6.2-1_i386 +libdbusmenu-qt-dev_0.9.0-1_i386 +libdc-dev_0.3.24~svn3121-2_i386 +libdc1394-22-dev_2.2.0-2_i386 +libdca-dev_0.0.5-5_i386 +libdcerpc-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcerpc-server-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcmtk2-dev_3.6.0-12_i386 +libdconf-dbus-1-dev_0.12.1-3_i386 +libdconf-dev_0.12.1-3_i386 +libddccontrol-dev_0.4.2-10_i386 +libdds-dev_2.1.2+ddd105-1_i386 +libdebconf-kde-dev_0.2-2_i386 +libdebconfclient0-dev_0.182_i386 +libdebian-installer4-dev_0.87_i386 +libdebug0-dev_0.4.4-1.1_i386 +libdecodeqr-dev_0.9.3-6.2_i386 +libdee-dev_1.0.10-3_i386 +libderiving-ocaml-dev_0.1.1a-3+b1_i386 +libderiving-ocsigen-ocaml-dev_0.3c-1_i386 +libdesktop-agnostic-dev_0.3.92+dfsg-1_i386 +libdessert0.87-dev_0.87.2-1_i386 +libdevhelp-dev_3.4.1-1_i386 +libdevil-dev_1.7.8-6.1+b1_i386 +libdevmapper-dev_2:1.02.74-8_i386 +libdhash-dev_0.1.3-2_i386 +libdiagnostics-dev_0.3.3-1.3_i386 +libdianewcanvas2-dev_0.6.10-5.4_i386 +libdieharder-dev_3.31.1-4_i386 +libdiet-admin2.8-dev_2.8.0-1+b1_i386 +libdiet-client2.8-dev_2.8.0-1+b1_i386 +libdiet-dagda2.8-dev_2.8.0-1+b1_i386 +libdiet-sed2.8-dev_2.8.0-1+b1_i386 +libdime-dev_0.20030921-2_all +libdirac-dev_1.0.2-6_i386 +libdirectfb-dev_1.2.10.0-5_i386 +libdisasm-dev_0.23-5_i386 +libdiscid0-dev_0.2.2-3_i386 +libdiscover-dev_2.1.2-5.2_i386 +libdispatch-dev_0~svn197-3.1_i386 +libdisplaymigration0-dev_0.28-10_i386 +libdistorm64-dev_1.7.30-1_i386 +libdivecomputer-dev_0.1.0-3_i386 +libdjconsole-dev_0.1.3-1_i386 +libdjvulibre-dev_3.5.25.3-1_i386 +libdkim-dev_1:1.0.21-3_i386 +libdlm-dev_3.0.12-3.2+deb7u2_i386 +libdlmcontrol-dev_3.0.12-3.2+deb7u2_i386 +libdlrestrictions-dev_0.15.3_i386 +libdm0-dev_2.2.10-1_i386 +libdmalloc-dev_5.5.2-5_i386 +libdmapsharing-3.0-dev_2.9.15-1_i386 +libdmraid-dev_1.0.0.rc16-4.2_i386 +libdmtcpaware-dev_1.2.5-1_i386 +libdmtx-dev_0.7.2-2+build1_i386 +libdmx-dev_1:1.1.2-1+deb7u1_i386 +libdnet-dev_2.60_i386 +libdockapp-dev_1:0.5.0-3_i386 +libdolfin1.0-dev_1.0.0-7_i386 +libdoodle-dev_0.7.0-5_i386 +libdose2-ocaml-dev_1.4.2-4+b3_i386 +libdose3-ocaml-dev_3.0.2-3_i386 +libdotconf-dev_1.0.13-3_i386 +libdpkg-dev_1.16.12_i386 +libdpm-dev_1.8.2-1+b2_i386 +libdrawtk-dev_2.0-2_i386 +libdrizzle-dev_1:7.1.36-stable-1_all +libdrizzledmessage-dev_1:7.1.36-stable-1_i386 +libdrm-dev_2.4.40-1~deb7u2_i386 +libdrumstick-dev_0.5.0-3_i386 +libdsdp-dev_5.8-9.1_i386 +libdshconfig1-dev_0.20.13-1_i386 +libdspam7-dev_3.10.1+dfsg-11_i386 +libdssi-ocaml-dev_0.1.0-1+b1_i386 +libdssialsacompat-dev_1.0.8a-1_i386 +libdtools-ocaml-dev_0.3.0-1_i386 +libdts-dev_0.0.5-5_i386 +libdumb1-dev_1:0.9.3-5.4_i386 +libdumbnet-dev_1.12-3.1_i386 +libdune-common-dev_2.2.0-1_i386 +libdune-geometry-dev_2.2.0-1_i386 +libdune-grid-dev_2.2.0-1_i386 +libdune-istl-dev_2.2.0-1_all +libdune-localfunctions-dev_2.2.0-1_all +libduo-dev_1.8-1_i386 +libduppy-ocaml-dev_0.4.2-1+b2_i386 +libdv4-dev_1.0.0-6_i386 +libdvb-dev_0.5.5.1-5.1_i386 +libdvbcsa-dev_1.1.0-2_i386 +libdvbpsi-dev_0.2.2-1_i386 +libdvdnav-dev_4.2.0+20120524-2_i386 +libdvdread-dev_4.2.0+20120521-2_i386 +libdw-dev_0.152-1+wheezy1_i386 +libdwarf-dev_20120410-2_i386 +libdx4-dev_1:4.4.4-4+b2_i386 +libdxflib-dev_2.2.0.0-8_i386 +libdynamite-dev_0.1.1-2_i386 +libeasy-format-ocaml-dev_1.0.0-1+b2_i386 +libeb16-dev_4.4.3-6_i386 +libebackend1.2-dev_3.4.4-3_i386 +libebml-dev_1.2.2-2_i386 +libebook1.2-dev_3.4.4-3_i386 +libecal1.2-dev_3.4.4-3_i386 +libecasoundc-dev_2.9.0-1_i386 +libecasoundc2.2-dev_2.9.0-1_all +libechonest-dev_1.2.1-1_i386 +libecm-dev_6.4.2-1_i386 +libecore-dev_1.2.0-2_i386 +libecpg-dev_9.1.13-0wheezy1_i386 +libecryptfs-dev_99-1_i386 +libedac-dev_0.18-1_i386 +libedata-book1.2-dev_3.4.4-3_i386 +libedata-cal1.2-dev_3.4.4-3_i386 +libedataserver1.2-dev_3.4.4-3_i386 +libedataserverui-3.0-dev_3.4.4-3_i386 +libedbus-dev_1.2.0-1_i386 +libedit-dev_2.11-20080614-5_i386 +libeditline-dev_1.12-6_i386 +libedje-dev_1.2.0-1_i386 +libee-dev_0.4.1-1_i386 +libeegdev-dev_0.2-3_i386 +libeet-dev_1.6.0-1_i386 +libefreet-dev_1.2.0-1_i386 +libegl1-mesa-dev_8.0.5-4+deb7u2_i386 +libeigen2-dev_2.0.17-1_i386 +libeigen3-dev_3.1.0-1_i386 +libeina-dev_1.2.0-2_i386 +libelektra-cpp-dev_0.7.1-1_i386 +libelektra-dev_0.7.1-1_i386 +libelektratools-dev_0.7.1-1_i386 +libelemental-dev_1.2.0-8_i386 +libelementary-dev_0.7.0.55225-1_i386 +libelf-dev_0.152-1+wheezy1_i386 +libelfg0-dev_0.8.13-3_i386 +libeliom-ocaml-dev_2.2.2-1_i386 +libelk0-dev_3.99.8-2_i386 +libelmer-dev_6.1.0.svn.5396.dfsg2-2_i386 +libembryo-dev_1.2.0-1_i386 +libemos-dev_000382+dfsg-2_i386 +libenca-dev_1.13-4_i386 +libenchant-dev_1.6.0-7_i386 +libenet-dev_1.3.3-2_i386 +libepc-dev_0.4.4-1_i386 +libepc-ui-dev_0.4.4-1_i386 +libepr-api2-dev_2.2-2_all +libepsilon-dev_0.9.1-2_i386 +libept-dev_1.0.9_i386 +libepub-dev_0.2.1-2+b1_i386 +liberis-1.3-dev_1.3.19-5_i386 +liberuby-dev_1.0.5-2.1_i386 +libescpr-dev_1.1.1-2_i386 +libesd0-dev_0.2.41-10+b1_i386 +libesmtp-dev_1.0.6-1+b1_i386 +libespeak-dev_1.46.02-2_i386 +libestools2.1-dev_1:2.1~release-5_i386 +libestr-dev_0.1.1-2_i386 +libethos-dev_0.2.2-3_i386 +libethos-ui-dev_0.2.2-3_i386 +libetpan-dev_1.0-5_i386 +libetsf-io-dev_1.0.3-4+b1_i386 +libeurodec1-dev_20061220+dfsg3-2_i386 +libev-dev_1:4.11-1_i386 +libev-libevent-dev_1:4.11-1_all +libeval0-dev_0.29.6-2_i386 +libevas-dev_1.2.0-2_i386 +libevd-0.1-dev_0.1.20-2_i386 +libevent-dev_2.0.19-stable-3_i386 +libeventdb-dev_0.90-5_i386 +libevince-dev_3.4.0-3.1_i386 +libevocosm-dev_4.0.2-2.1_i386 +libevs-dev_1.4.2-3_i386 +libevtlog-dev_0.2.12-5_i386 +libewf-dev_20100226-1+b1_i386 +libexchangemapi-1.0-dev_3.4.4-1_i386 +libexempi-dev_2.2.0-1_i386 +libexif-dev_0.6.20-3_i386 +libexif-gtk-dev_0.3.5-5_i386 +libexiv2-dev_0.23-1_i386 +libexo-1-dev_0.6.2-5_i386 +libexodusii-dev_5.14.dfsg.1-2+b1_i386 +libexosip2-dev_3.6.0-4_i386 +libexpat-ocaml-dev_0.9.1+debian1-7+b2_i386 +libexpat1-dev_2.1.0-1+deb7u1_i386 +libexpect-ocaml-dev_0.0.2-1+b6_i386 +libexplain-dev_0.52.D002-1_i386 +libextlib-ocaml-dev_1.5.2-1+b1_i386 +libextractor-dev_1:0.6.3-5_i386 +libextractor-java-dev_0.6.0-6_i386 +libexttextcat-dev_3.2.0-2_i386 +libextunix-ocaml-dev_0.0.5-2_i386 +libeztrace-dev_0.7-2-4_i386 +libf2c2-dev_20090411-2_i386 +libfaad-dev_2.7-8_i386 +libfaad-ocaml-dev_0.3.0-1+b1_i386 +libfacile-ocaml-dev_1.1-8+b1_i386 +libfaifa-dev_0.2~svn82-1_i386 +libfakekey-dev_0.1-7_i386 +libfam-dev_2.7.0-17_i386 +libfann-dev_2.1.0~beta~dfsg-8_i386 +libfarstream-0.1-dev_0.1.2-1_i386 +libfastjet-dev_3.0.2+dfsg-2_i386 +libfastjet-fortran-dev_3.0.2+dfsg-2_i386 +libfastjetplugins-dev_3.0.2+dfsg-2_i386 +libfastjettools-dev_3.0.2+dfsg-2_i386 +libfauhdli-dev_20110812-1_i386 +libfcgi-dev_2.4.0-8.1_i386 +libfdt-dev_1.3.0-4_i386 +libfence-dev_3.0.12-3.2+deb7u2_i386 +libffado-dev_2.0.99+svn2171-2_i386 +libffcall1-dev_1.10+cvs20100619-2_i386 +libffi-dev_3.0.10-3_i386 +libffindex0-dev_0.9.6.1-1_i386 +libffmpegthumbnailer-dev_2.0.7-2_i386 +libffms2-dev_2.17-1_i386 +libfftw3-dev_3.3.2-3.1_i386 +libfftw3-mpi-dev_3.3.2-3.1_i386 +libfields-camlp4-dev_107.01-1+b2_i386 +libfileutils-ocaml-dev_0.4.2-1+b2_i386 +libfindlib-ocaml-dev_1.3.1-1_i386 +libfiredns-dev_0.9.12+dfsg-3_i386 +libfirestring-dev_0.9.12-8_i386 +libfishsound1-dev_1.0.0-1.1_i386 +libfiu-dev_0.90-3_i386 +libfixposix-dev_20110316.git47f17f7-1_i386 +libfko0-dev_2.0.0rc2-2+deb7u2_i386 +libflac++-dev_1.2.1-6_i386 +libflac-dev_1.2.1-6_i386 +libflac-ocaml-dev_0.1.1-1_i386 +libflake-dev_0.11-2_i386 +libflann-dev_1.7.1-4_i386 +libflatzebra-dev_0.1.5-4+b1_i386 +libflickrnet-cil-dev_1:2.2.0-4_all +libflorist2011-dev_2011-1_i386 +libflowcanvas-dev_0.7.1+dfsg0-0.2_i386 +libfltk1.1-dev_1.1.10-14_i386 +libfltk1.3-dev_1.3.0-8_i386 +libfluidsynth-dev_1.1.5-2_i386 +libfm-dev_0.1.17-2.1_i386 +libfolia1-dev_0.9-2_i386 +libfolks-dev_0.6.9-1+b1_i386 +libfolks-eds-dev_0.6.9-1+b1_i386 +libfolks-telepathy-dev_0.6.9-1+b1_i386 +libfontconfig1-dev_2.9.0-7.1_i386 +libfontenc-dev_1:1.1.1-1_i386 +libfontforge-dev_0.0.20120101+git-2_i386 +libforms-dev_1.0.93sp1-2_i386 +libformsgl-dev_1.0.93sp1-2_i386 +libfox-1.6-dev_1.6.45-1_i386 +libfprint-dev_1:0.4.0-4-gdfff16f-4_i386 +libfreecell-solver-dev_3.12.0-1_i386 +libfreefem++-dev_3.19.1-1_i386 +libfreefem-dev_3.5.8-5_i386 +libfreehdl0-dev_0.0.7-1.1_i386 +libfreeimage-dev_3.15.1-1+b1_i386 +libfreeipmi-dev_1.1.5-3_i386 +libfreenect-dev_1:0.1.2+dfsg-6_i386 +libfreeradius-dev_2.1.12+dfsg-1.2_i386 +libfreerdp-dev_1.0.1-1.1+deb7u3_i386 +libfreetype6-dev_2.4.9-1.1_i386 +libfreexl-dev_1.0.0b-1_i386 +libfribidi-dev_0.19.2-3_i386 +libfs-dev_2:1.0.4-1+deb7u1_i386 +libfso-glib-dev_2012.05.24.1-1.1_i386 +libfsobasics-dev_0.11.0-1.1_i386 +libfsoframework-dev_0.11.0-1.1_i386 +libfsoresource-dev_0.11.0-1.1_i386 +libfsosystem-dev_0.11.0-1_i386 +libfsotransport-dev_0.11.1-2.1_i386 +libfsplib-dev_0.11-2_i386 +libfstrcmp-dev_0.4.D001-1+deb7u1_i386 +libftdi-dev_0.20-1+b1_i386 +libftdipp-dev_0.20-1+b1_i386 +libftgl-dev_2.1.3~rc5-4_i386 +libfuntools-dev_1.4.4-3_i386 +libfuse-dev_2.9.0-2+deb7u1_i386 +libfuzzy-dev_2.7-2_i386 +libfxt-dev_0.2.6-2_i386 +libg15-dev_1.2.7-2_i386 +libg15daemon-client-dev_1.9.5.3-8.2_i386 +libg15render-dev_1.3.0~svn316-2.2_i386 +libg2-dev_0.72-2.1_i386 +libg3d-dev_0.0.8-17_i386 +libga-dev_2.4.7-3_i386 +libgadap-dev_2.0-1_i386 +libgadu-dev_1:1.11.2-1+deb7u1_i386 +libgail-3-dev_3.4.2-7_i386 +libgail-dev_2.24.10-2_i386 +libgalax-ocaml-dev_1.1-10+b3_i386 +libgambc4-dev_4.2.8-1.1_i386 +libgamin-dev_0.1.10-4.1_i386 +libgammu-dev_1.31.90-1+b1_i386 +libganglia1-dev_3.3.8-1+nmu1_i386 +libganv-dev_0~svn4468~dfsg0-1_i386 +libgarcon-1-0-dev_0.1.12-1_i386 +libgarmin-dev_0~svn320-3_i386 +libgatos-dev_0.0.5-19_i386 +libgavl-dev_1.4.0-1_i386 +libgavl-ocaml-dev_0.1.4-1+b1_i386 +libgbm-dev_8.0.5-4+deb7u2_i386 +libgc-dev_1:7.1-9.1_i386 +libgcal-dev_0.9.6-3_i386 +libgccxml-dev_0.9.0+cvs20120420-4_i386 +libgcgi-dev_0.9.5.dfsg-7_i386 +libgcj12-dev_4.6.3-1_i386 +libgcj13-dev_4.7.2-3_i386 +libgck-1-dev_3.4.1-3_i386 +libgconf-bridge-dev_0.1-2.2_i386 +libgconf2-dev_3.2.5-1+build1_i386 +libgconf2.0-cil-dev_2.24.2-3_all +libgconfmm-2.6-dev_2.28.0-1_i386 +libgcr-3-dev_3.4.1-3_i386 +libgcroots-dev_0.8.5-2.1_i386 +libgcrypt11-dev_1.5.0-5+deb7u1_i386 +libgctp-dev_1.0-1_i386 +libgd-gd2-noxpm-ocaml-dev_1.0~alpha5-5_i386 +libgd2-noxpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgda-5.0-dev_5.0.3-2_i386 +libgdal-dev_1.9.0-3.1_i386 +libgdal1-dev_1.9.0-3.1_all +libgdata-cil-dev_2.1.0.0-1_all +libgdata-dev_0.12.0-1_i386 +libgdb-dev_7.4.1+dfsg-0.1_i386 +libgdbm-dev_1.8.3-11_i386 +libgdchart-gd2-noxpm-dev_0.11.5-7+b1_i386 +libgdchart-gd2-xpm-dev_0.11.5-7+b1_i386 +libgdcm2-dev_2.2.0-14.1_i386 +libgdf-dev_0.1.2-2_i386 +libgdict-1.0-dev_3.4.0-2_i386 +libgdk-pixbuf2.0-dev_2.26.1-1_i386 +libgdkcutter-pixbuf-dev_1.1.7-1.2_i386 +libgdl-3-dev_3.4.2-1_i386 +libgdome2-cpp-smart-dev_0.2.6-6+b1_i386 +libgdome2-dev_0.8.1+debian-4.1_i386 +libgdome2-ocaml-dev_0.2.6-6+b1_i386 +libgdu-dev_3.0.2-3_i386 +libgdu-gtk-dev_3.0.2-3_i386 +libgeant321-2-dev_1:3.21.14.dfsg-10_i386 +libgearman-dev_0.33-2_i386 +libgecode-dev_3.7.3-1_i386 +libgeda-dev_1:1.6.2-4.3_i386 +libgee-dev_0.6.4-2_i386 +libgegl-dev_0.2.0-2+nmu1_i386 +libgeier-dev_0.13-1+b1_i386 +libgenders0-dev_1.18-1_i386 +libgenome-1.3-0-dev_1.3.1-3_i386 +libgensec-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libgeoclue-dev_0.12.0-4_i386 +libgeocode-glib-dev_0.99.0-1_i386 +libgeographiclib-dev_1.21-1_i386 +libgeoip-dev_1.4.8+dfsg-3_i386 +libgeomview-dev_1.9.4-3_i386 +libgeos++-dev_3.3.3-1.1_i386 +libgeos-dev_3.3.3-1.1_i386 +libgeotiff-dev_1.3.0+dfsg-3_i386 +libgeotranz3-dev_3.1-2.1_i386 +libges-0.10-dev_0.10.1-2_i386 +libgetdata-dev_0.7.3-6_i386 +libgetfem++-dev_4.1.1+dfsg1-11_i386 +libgetopt++-dev_0.0.2-p22-3_i386 +libgetopt-ocaml-dev_0.0.20040811-10+b3_i386 +libgettext-ocaml-dev_0.3.4-1+b2_i386 +libgexiv2-dev_0.4.1-3_i386 +libgfarm-dev_2.4.1-1.1_i386 +libgflags-dev_2.0-1_i386 +libgfshare-dev_1.0.5-2_i386 +libghc-acid-state-dev_0.6.3-1+b2_i386 +libghc-active-dev_0.1.0.1-2+b2_i386 +libghc-adjunctions-dev_2.4.0.2-1_i386 +libghc-aeson-dev_0.6.0.2-1+b4_i386 +libghc-agda-dev_2.3.0.1-2_i386 +libghc-algebra-dev_2.1.1.2-1_i386 +libghc-alut-dev_2.1.0.2-4+b1_i386 +libghc-ami-dev_0.1-1+b5_i386 +libghc-ansi-terminal-dev_0.5.5-3+b1_i386 +libghc-ansi-wl-pprint-dev_0.6.4-1+b1_i386 +libghc-arrows-dev_0.4.4.0-3+b1_i386 +libghc-asn1-data-dev_0.6.1.3-2+b3_i386 +libghc-attempt-dev_0.4.0-1+b2_i386 +libghc-attoparsec-conduit-dev_0.4.0.1-1_i386 +libghc-attoparsec-dev_0.10.1.1-2+b1_i386 +libghc-attoparsec-enumerator-dev_0.3-3+b3_i386 +libghc-augeas-dev_0.6.1-1_i386 +libghc-authenticate-dev_1.2.1.1-2+b1_i386 +libghc-base-unicode-symbols-dev_0.2.2.3-1+b1_i386 +libghc-base16-bytestring-dev_0.1.1.4-2+b1_i386 +libghc-base64-bytestring-dev_0.1.1.1-2_i386 +libghc-bifunctors-dev_0.1.3.3-1+b1_i386 +libghc-binary-shared-dev_0.8.1-1+b1_i386 +libghc-bindings-dsl-dev_1.0.15-1+b1_i386 +libghc-bindings-gpgme-dev_0.1.4-1_i386 +libghc-bindings-libzip-dev_0.10-2_i386 +libghc-bitarray-dev_0.0.1-2+b1_i386 +libghc-blaze-builder-conduit-dev_0.4.0.2-1_i386 +libghc-blaze-builder-dev_0.3.1.0-1+b2_i386 +libghc-blaze-builder-enumerator-dev_0.2.0.4-1+b1_i386 +libghc-blaze-html-dev_0.4.3.1-3+b2_i386 +libghc-blaze-markup-dev_0.5.1.0-1_i386 +libghc-blaze-textual-dev_0.2.0.6-2+b2_i386 +libghc-bloomfilter-dev_1.2.6.8-1_i386 +libghc-boolean-dev_0.0.1-2+b1_i386 +libghc-boomerang-dev_1.3.1-1_i386 +libghc-brainfuck-dev_0.1-2+b2_i386 +libghc-byteorder-dev_1.0.3-2+b1_i386 +libghc-bytestring-lexing-dev_0.4.0-1+b1_i386 +libghc-bytestring-mmap-dev_0.2.2-2+b1_i386 +libghc-bytestring-nums-dev_0.3.5-2+b1_i386 +libghc-bytestring-show-dev_0.3.5.1-1+b1_i386 +libghc-bzlib-dev_0.5.0.3-2+b1_i386 +libghc-cabal-file-th-dev_0.2.2-1_i386 +libghc-cairo-dev_0.12.3-1+b1_i386 +libghc-case-insensitive-dev_0.4.0.1-2+b2_i386 +libghc-categories-dev_1.0.3-1+b1_i386 +libghc-cautious-file-dev_1.0.1-1_i386 +libghc-cereal-conduit-dev_0.5-1+b1_i386 +libghc-cereal-dev_0.3.5.2-1_i386 +libghc-certificate-dev_1.2.3-2_i386 +libghc-cgi-dev_3001.1.8.2-2+b3_i386 +libghc-chart-dev_0.15-1+b2_i386 +libghc-chell-dev_0.3-1_i386 +libghc-citeproc-hs-dev_0.3.4-1+b4_i386 +libghc-clientsession-dev_0.7.5-3+b1_i386 +libghc-clock-dev_0.2.0.0-2+b1_i386 +libghc-cmdargs-dev_0.9.5-1+b1_i386 +libghc-colour-dev_2.3.3-1+b1_i386 +libghc-comonad-dev_1.1.1.5-1+b1_i386 +libghc-comonad-transformers-dev_2.1.1.1-1+b1_i386 +libghc-comonads-fd-dev_2.1.1.2-1+b1_i386 +libghc-conduit-dev_0.4.2-2_i386 +libghc-configfile-dev_1.0.6-4+b3_i386 +libghc-configurator-dev_0.2.0.0-1+b2_i386 +libghc-contravariant-dev_0.2.0.2-1+b1_i386 +libghc-convertible-dev_1.0.11.0-3+b3_i386 +libghc-cookie-dev_0.4.0-1+b3_i386 +libghc-cpphs-dev_1.13.3-2+b1_i386 +libghc-cprng-aes-dev_0.2.3-3+b4_i386 +libghc-cpu-dev_0.1.1-1_i386 +libghc-criterion-dev_0.6.0.1-3+b4_i386 +libghc-crypto-api-dev_0.10.2-1+b2_i386 +libghc-crypto-conduit-dev_0.3.2-1+b1_i386 +libghc-crypto-dev_4.2.4-1+b1_i386 +libghc-crypto-pubkey-types-dev_0.1.1-1+b3_i386 +libghc-cryptocipher-dev_0.3.5-1+b1_i386 +libghc-cryptohash-dev_0.7.5-1+b2_i386 +libghc-css-text-dev_0.1.1-3+b2_i386 +libghc-csv-conduit-dev_0.2-1_i386 +libghc-csv-dev_0.1.2-2+b3_i386 +libghc-curl-dev_1.3.7-1+b1_i386 +libghc-darcs-dev_2.8.1-1+b1_i386 +libghc-data-accessor-dev_0.2.2.2-1+b1_i386 +libghc-data-accessor-mtl-dev_0.2.0.3-1+b1_i386 +libghc-data-accessor-template-dev_0.2.1.9-1+b2_i386 +libghc-data-binary-ieee754-dev_0.4.2.1-3+b1_i386 +libghc-data-default-dev_0.4.0-1_i386 +libghc-data-inttrie-dev_0.0.7-1+b1_i386 +libghc-data-lens-dev_2.10.0-1+b1_i386 +libghc-data-memocombinators-dev_0.4.3-1+b1_i386 +libghc-dataenc-dev_0.14.0.3-1+b1_i386 +libghc-datetime-dev_0.2.1-3_i386 +libghc-dbus-dev_0.10.3-1_i386 +libghc-debian-dev_3.64-3_i386 +libghc-diagrams-cairo-dev_0.5.0.2-1_i386 +libghc-diagrams-core-dev_0.5.0.1-1+b1_i386 +libghc-diagrams-dev_0.5-2_all +libghc-diagrams-lib-dev_0.5-2_i386 +libghc-diff-dev_0.1.3-1+b1_i386 +libghc-digest-dev_0.0.1.0-1+b1_i386 +libghc-dimensional-dev_0.10.1.2-2+b1_i386 +libghc-directory-tree-dev_0.10.0-2+b1_i386 +libghc-distributive-dev_0.2.2-1+b1_i386 +libghc-dlist-dev_0.5-3+b1_i386 +libghc-download-curl-dev_0.1.3-3+b3_i386 +libghc-dpkg-dev_0.0.3-1_i386 +libghc-dyre-dev_0.8.7-1_i386 +libghc-edison-api-dev_1.2.1-18+b1_i386 +libghc-edison-core-dev_1.2.1.3-9+b1_i386 +libghc-edit-distance-dev_0.2.1-2_i386 +libghc-editline-dev_0.2.1.0-5+b1_i386 +libghc-ekg-dev_0.3.1.0-1+b2_i386 +libghc-email-validate-dev_0.2.8-1+b3_i386 +libghc-entropy-dev_0.2.1-2+b1_i386 +libghc-enumerator-dev_0.4.19-1+b1_i386 +libghc-erf-dev_2.0.0.0-2+b1_i386 +libghc-event-list-dev_0.1.0.1-1+b1_i386 +libghc-exception-transformers-dev_0.3.0.2-1+b1_i386 +libghc-executable-path-dev_0.0.3-1+b1_i386 +libghc-explicit-exception-dev_0.1.7-1+b1_i386 +libghc-failure-dev_0.2.0.1-1+b1_i386 +libghc-fast-logger-dev_0.0.2-1+b2_i386 +libghc-fastcgi-dev_3001.0.2.3-3+b3_i386 +libghc-fclabels-dev_1.1.3-1+b1_i386 +libghc-feed-dev_0.3.8-3_i386 +libghc-fgl-dev_5.4.2.4-2+b2_i386 +libghc-file-embed-dev_0.0.4.4-1_i386 +libghc-filemanip-dev_0.3.5.2-2+b2_i386 +libghc-filestore-dev_0.5-1_i386 +libghc-filesystem-conduit-dev_0.4.0-1_i386 +libghc-free-dev_2.1.1.1-1+b1_i386 +libghc-ftphs-dev_1.0.8-1+b3_i386 +libghc-gconf-dev_0.12.1-1+b1_i386 +libghc-gd-dev_3000.7.3-1_i386 +libghc-ghc-events-dev_0.4.0.0-2+b1_i386 +libghc-ghc-mtl-dev_1.0.1.1-1+b3_i386 +libghc-ghc-paths-dev_0.1.0.8-2+b1_i386 +libghc-ghc-syb-utils-dev_0.2.1.0-1+b3_i386 +libghc-gio-dev_0.12.3-1+b1_i386 +libghc-github-dev_0.4.0-2_i386 +libghc-gitit-dev_0.10.0.1-1+b1_i386 +libghc-glade-dev_0.12.1-1+b3_i386 +libghc-glfw-dev_0.5.0.1-1+b1_i386 +libghc-glib-dev_0.12.2-1+b1_i386 +libghc-glut-dev_2.1.2.2-1_i386 +libghc-gnuidn-dev_0.2-2+b2_i386 +libghc-gnutls-dev_0.1.2-1+b1_i386 +libghc-gsasl-dev_0.3.4-1+b1_i386 +libghc-gstreamer-dev_0.12.1-1+b2_i386 +libghc-gtk-dev_0.12.3-1+b2_i386 +libghc-gtkglext-dev_0.12.1-1+b3_i386 +libghc-gtksourceview2-dev_0.12.3-1+b3_i386 +libghc-haddock-dev_2.10.0-1+b2_i386 +libghc-hakyll-dev_3.2.7.2-1+b5_i386 +libghc-hamlet-dev_1.0.1.3-1+b1_i386 +libghc-happstack-dev_7.0.0-1+b1_i386 +libghc-happstack-server-dev_7.0.1-1+b1_i386 +libghc-harp-dev_0.4-3+b1_i386 +libghc-hashable-dev_1.1.2.3-1+b2_i386 +libghc-hashed-storage-dev_0.5.9-2+b2_i386 +libghc-hashmap-dev_1.3.0.1-1+b2_i386 +libghc-hashtables-dev_1.0.1.4-1+b1_i386 +libghc-haskeline-dev_0.6.4.7-1+b1_i386 +libghc-haskell-lexer-dev_1.0-3+b1_i386 +libghc-haskell-src-dev_1.0.1.5-1+b2_i386 +libghc-haskelldb-dev_2.1.1-5+b1_i386 +libghc-haskelldb-hdbc-dev_2.1.0-4_i386 +libghc-haskelldb-hdbc-odbc-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-postgresql-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-sqlite3-dev_2.1.0-3_i386 +libghc-haskore-dev_0.2.0.3-2_i386 +libghc-hastache-dev_0.3.3-2+b3_i386 +libghc-haxml-dev_1:1.22.5-2+b2_i386 +libghc-haxr-dev_3000.8.5-1+b3_i386 +libghc-hcard-dev_0.0-2+b2_i386 +libghc-hcwiid-dev_0.0.1-3+b1_i386 +libghc-hdbc-dev_2.3.1.1-1+b3_i386 +libghc-hdbc-odbc-dev_2.2.3.0-5+b3_i386 +libghc-hdbc-postgresql-dev_2.3.2.1-1+b3_i386 +libghc-hdbc-sqlite3-dev_2.3.3.0-1+b3_i386 +libghc-hfuse-dev_0.2.4.1-1_i386 +libghc-highlighting-kate-dev_0.5.1-1_i386 +libghc-hinotify-dev_0.3.2-1+b1_i386 +libghc-hint-dev_0.3.3.4-2+b4_i386 +libghc-hipmunk-dev_5.2.0.8-1+b1_i386 +libghc-hjavascript-dev_0.4.7-3+b1_i386 +libghc-hjscript-dev_0.5.0-3+b2_i386 +libghc-hjsmin-dev_0.1.1-1+b2_i386 +libghc-hlint-dev_1.8.28-1+b3_i386 +libghc-hoauth-dev_0.3.4-1+b1_i386 +libghc-hostname-dev_1.0-4+b1_i386 +libghc-hs-bibutils-dev_4.12-5+b2_i386 +libghc-hs3-dev_0.5.6-2+b4_i386 +libghc-hscolour-dev_1.19-3+b1_i386 +libghc-hscurses-dev_1.4.1.0-1+b2_i386 +libghc-hsemail-dev_1.7.1-2+b3_i386 +libghc-hsh-dev_2.0.3-6+b3_i386 +libghc-hslogger-dev_1.1.4+dfsg1-2+b3_i386 +libghc-hsp-dev_0.6.1-2+b3_i386 +libghc-hspec-dev_1.1.0-1+b1_i386 +libghc-hsql-dev_1.8.1-4_i386 +libghc-hsql-mysql-dev_1.8.1-4+b1_i386 +libghc-hsql-odbc-dev_1.8.1.1-2_i386 +libghc-hsql-postgresql-dev_1.8.1-3_i386 +libghc-hsql-sqlite3-dev_1.8.1-2_i386 +libghc-hssyck-dev_0.50-2+b2_i386 +libghc-hstringtemplate-dev_0.6.8-1_i386 +libghc-hsx-dev_0.9.1-3_i386 +libghc-html-conduit-dev_0.0.1-2_i386 +libghc-html-dev_1.0.1.2-5+b1_i386 +libghc-http-conduit-dev_1.4.1.6-3_i386 +libghc-http-date-dev_0.0.2-1+b2_i386 +libghc-http-dev_1:4000.2.3-1+b2_i386 +libghc-http-types-dev_0.6.11-1_i386 +libghc-hunit-dev_1.2.4.2-2+b1_i386 +libghc-hxt-cache-dev_9.0.2-2+b3_i386 +libghc-hxt-charproperties-dev_9.1.1-2+b1_i386 +libghc-hxt-curl-dev_9.1.1-1+b4_i386 +libghc-hxt-dev_9.2.2-2+b3_i386 +libghc-hxt-http-dev_9.1.4-2+b3_i386 +libghc-hxt-regex-xmlschema-dev_9.0.4-2+b3_i386 +libghc-hxt-relaxng-dev_9.1.4-1+b3_i386 +libghc-hxt-tagsoup-dev_9.1.1-1+b4_i386 +libghc-hxt-unicode-dev_9.0.2-2+b1_i386 +libghc-hxt-xpath-dev_9.1.2-1+b4_i386 +libghc-hxt-xslt-dev_9.1.1-1+b3_i386 +libghc-iconv-dev_0.4.1.0-2+b1_i386 +libghc-ieee754-dev_0.7.3-1+b1_i386 +libghc-ifelse-dev_0.85-4+b1_i386 +libghc-io-choice-dev_0.0.1-1+b3_i386 +libghc-io-storage-dev_0.3-2+b1_i386 +libghc-iospec-dev_0.2.5-1+b2_i386 +libghc-irc-dev_0.5.0.0-1+b3_i386 +libghc-iteratee-dev_0.8.8.2-2+b1_i386 +libghc-ixset-dev_1.0.3-2+b1_i386 +libghc-json-dev_0.5-2+b2_i386 +libghc-keys-dev_2.1.3.2-1+b1_i386 +libghc-knob-dev_0.1.1-1_i386 +libghc-lambdabot-utils-dev_4.2.1-3+b3_i386 +libghc-language-c-dev_0.4.2-2+b2_i386 +libghc-language-haskell-extract-dev_0.2.1-4+b1_i386 +libghc-language-javascript-dev_0.5.4-1+b2_i386 +libghc-largeword-dev_1.0.1-2+b1_i386 +libghc-lazysmallcheck-dev_0.6-1+b1_i386 +libghc-ldap-dev_0.6.6-4.1+b1_i386 +libghc-leksah-server-dev_0.12.0.4-3_i386 +libghc-libtagc-dev_0.12.0-2+b1_i386 +libghc-libxml-sax-dev_0.7.2-2+b1_i386 +libghc-libzip-dev_0.10-1+b2_i386 +libghc-lifted-base-dev_0.1.1-1+b1_i386 +libghc-listlike-dev_3.1.4-1+b1_i386 +libghc-llvm-base-dev_3.0.1.0-1_i386 +libghc-llvm-dev_3.0.1.0-1+b1_i386 +libghc-logict-dev_0.5.0.1-1+b1_i386 +libghc-ltk-dev_0.12.0.0-2+b1_i386 +libghc-maccatcher-dev_2.1.5-2+b3_i386 +libghc-magic-dev_1.0.8-8+b1_i386 +libghc-markov-chain-dev_0.0.3.2-1+b1_i386 +libghc-math-functions-dev_0.1.1.0-2+b2_i386 +libghc-maths-dev_0.4.3-1+b1_i386 +libghc-maybet-dev_0.1.2-3+b2_i386 +libghc-mbox-dev_0.1-2+b1_i386 +libghc-memotrie-dev_0.5-1_i386 +libghc-mersenne-random-dev_1.0.0.1-2+b1_i386 +libghc-midi-dev_0.2.0.1-1+b1_i386 +libghc-mime-mail-dev_0.4.1.1-2+b3_i386 +libghc-missingh-dev_1.1.0.3-6+b3_i386 +libghc-mmap-dev_0.5.7-2+b1_i386 +libghc-monad-control-dev_0.3.1.3-1+b1_i386 +libghc-monad-loops-dev_0.3.2.0-1_i386 +libghc-monad-par-dev_0.1.0.3-2+b1_i386 +libghc-monadcatchio-mtl-dev_0.3.0.4-2+b2_i386 +libghc-monadcatchio-transformers-dev_0.3.0.0-2+b1_i386 +libghc-monadcryptorandom-dev_0.4.1-1+b2_i386 +libghc-monadrandom-dev_0.1.6-2+b2_i386 +libghc-monads-tf-dev_0.1.0.0-1+b2_i386 +libghc-monoid-transformer-dev_0.0.2-3+b1_i386 +libghc-mtl-dev_2.1.1-1_i386 +libghc-mtlparse-dev_0.1.2-2+b2_i386 +libghc-murmur-hash-dev_0.1.0.5-2+b1_i386 +libghc-mwc-random-dev_0.11.0.0-4+b1_i386 +libghc-ncurses-dev_0.2.1-1+b1_i386 +libghc-netwire-dev_3.1.0-2+b5_i386 +libghc-network-conduit-dev_0.4.0.1-2_i386 +libghc-network-dev_2.3.0.13-1+b2_i386 +libghc-network-protocol-xmpp-dev_0.4.3-1_i386 +libghc-newtype-dev_0.2-1_i386 +libghc-non-negative-dev_0.1-2+b1_i386 +libghc-numbers-dev_2009.8.9-2+b1_i386 +libghc-numeric-quest-dev_0.2-1+b1_i386 +libghc-numinstances-dev_1.0-2+b1_i386 +libghc-numtype-dev_1.0-2+b1_i386 +libghc-oeis-dev_0.3.1-2+b3_i386 +libghc-openal-dev_1.3.1.3-4+b1_i386 +libghc-opengl-dev_2.2.3.1-1+b1_i386 +libghc-openpgp-asciiarmor-dev_0.1-1+b2_i386 +libghc-options-dev_0.1.1-1_i386 +libghc-pandoc-dev_1.9.4.2-2_i386 +libghc-pandoc-types-dev_1.9.1-1+b2_i386 +libghc-pango-dev_0.12.2-1+b2_i386 +libghc-parallel-dev_3.2.0.2-2+b1_i386 +libghc-parseargs-dev_0.1.3.2-2+b1_i386 +libghc-parsec2-dev_2.1.0.1-6+b1_i386 +libghc-parsec3-dev_3.1.2-1+b3_i386 +libghc-pastis-dev_0.1.2-2+b3_i386 +libghc-path-pieces-dev_0.1.0-1+b2_i386 +libghc-patience-dev_0.1.1-1_i386 +libghc-pcre-light-dev_0.4-3+b1_i386 +libghc-pem-dev_0.1.1-1+b3_i386 +libghc-persistent-dev_0.9.0.4-2_i386 +libghc-persistent-sqlite-dev_0.9.0.2-2_i386 +libghc-persistent-template-dev_0.9.0.2-1_i386 +libghc-polyparse-dev_1.7-1+b2_i386 +libghc-pool-conduit-dev_0.1.0.2-1_i386 +libghc-postgresql-libpq-dev_0.8.2-1_i386 +libghc-postgresql-simple-dev_0.1.4.3-1_i386 +libghc-pretty-show-dev_1.1.1-4+b1_i386 +libghc-primes-dev_0.2.1.0-2+b1_i386 +libghc-primitive-dev_0.4.1-1+b1_i386 +libghc-psqueue-dev_1.1-2+b1_i386 +libghc-puremd5-dev_2.1.0.3-2+b4_i386 +libghc-pwstore-fast-dev_2.2-2+b4_i386 +libghc-quickcheck1-dev_1.2.0.1-2+b1_i386 +libghc-quickcheck2-dev_2.4.2-1+b1_i386 +libghc-random-dev_1.0.1.1-1+b1_i386 +libghc-random-shuffle-dev_0.0.3-2+b2_i386 +libghc-ranged-sets-dev_0.3.0-2+b1_i386 +libghc-ranges-dev_0.2.4-2+b1_i386 +libghc-reactive-banana-dev_0.6.0.0-1+b3_i386 +libghc-readline-dev_1.0.1.0-3+b1_i386 +libghc-recaptcha-dev_0.1-4+b3_i386 +libghc-regex-base-dev_0.93.2-2+b2_i386 +libghc-regex-compat-dev_0.95.1-2+b1_i386 +libghc-regex-pcre-dev_0.94.2-2+b1_i386 +libghc-regex-posix-dev_0.95.1-2+b1_i386 +libghc-regex-tdfa-dev_1.1.8-2+b1_i386 +libghc-regex-tdfa-utf8-dev_1.0-5+b3_i386 +libghc-regexpr-dev_0.5.4-2+b2_i386 +libghc-representable-functors-dev_2.4.0.2-1+b1_i386 +libghc-representable-tries-dev_2.4.0.2-1_i386 +libghc-resource-pool-dev_0.2.1.0-2+b4_i386 +libghc-resourcet-dev_0.3.2.1-1+b1_i386 +libghc-rsa-dev_1.2.1.0-1+b1_i386 +libghc-safe-dev_0.3.3-1+b1_i386 +libghc-safecopy-dev_0.6.1-1+b1_i386 +libghc-sdl-dev_0.6.3-1+b1_i386 +libghc-sdl-gfx-dev_0.6.0-3+b1_i386 +libghc-sdl-image-dev_0.6.1-3+b1_i386 +libghc-sdl-mixer-dev_0.6.1-3+b1_i386 +libghc-sdl-ttf-dev_0.6.1-3+b1_i386 +libghc-semigroupoids-dev_1.3.1.2-1+b1_i386 +libghc-semigroups-dev_0.8.3.2-1_i386 +libghc-sendfile-dev_0.7.6-1+b2_i386 +libghc-sha-dev_1.5.0.1-1_i386 +libghc-shakespeare-css-dev_1.0.1.2-1+b1_i386 +libghc-shakespeare-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-i18n-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-js-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-text-dev_1.0.0.2-1+b1_i386 +libghc-shellac-dev_0.9.5.1-2+b2_i386 +libghc-show-dev_0.4.1.2-1+b2_i386 +libghc-silently-dev_1.1.4-1+b2_i386 +libghc-simple-sendfile-dev_0.2.3-1+b2_i386 +libghc-simpleea-dev_0.1.1-2+b2_i386 +libghc-simpleirc-dev_0.2.1-2+b3_i386 +libghc-skein-dev_0.1.0.7-2+b1_i386 +libghc-smallcheck-dev_0.6-1+b1_i386 +libghc-smtpclient-dev_1.0.4-3+b3_i386 +libghc-snap-core-dev_0.8.1-1+b4_i386 +libghc-snap-server-dev_0.8.1.1-1_i386 +libghc-socks-dev_0.4.1-1+b4_i386 +libghc-split-dev_0.1.4.2-2_i386 +libghc-src-exts-dev_1.11.1-3+b1_i386 +libghc-statevar-dev_1.0.0.0-2+b1_i386 +libghc-static-hash-dev_0.0.1-3+b2_i386 +libghc-statistics-dev_0.10.1.0-2+b1_i386 +libghc-stm-dev_2.3-1_i386 +libghc-stream-dev_0.4.6-1+b1_i386 +libghc-strict-concurrency-dev_0.2.4.1-2+b1_i386 +libghc-strict-dev_0.3.2-2+b1_i386 +libghc-strptime-dev_1.0.6-1_i386 +libghc-svgcairo-dev_0.12.1-1+b2_i386 +libghc-syb-dev_0.3.6.1-1_i386 +libghc-syb-with-class-dev_0.6.1.3-1+b1_i386 +libghc-syb-with-class-instances-text-dev_0.0.1-3+b2_i386 +libghc-system-fileio-dev_0.3.8-1_i386 +libghc-system-filepath-dev_0.4.6-1+b2_i386 +libghc-tagged-dev_0.4.2.1-1_i386 +libghc-tagsoup-dev_0.12.6-1+b3_i386 +libghc-tagstream-conduit-dev_0.3.2-1_i386 +libghc-tar-dev_0.3.2.0-2+b1_i386 +libghc-template-dev_0.2.0.7-1+b1_i386 +libghc-temporary-dev_1.1.2.3-1+b1_i386 +libghc-terminfo-dev_0.3.2.3-1+b1_i386 +libghc-test-framework-dev_0.6-1+b1_i386 +libghc-test-framework-hunit-dev_0.2.7-1+b3_i386 +libghc-test-framework-quickcheck2-dev_0.2.12.1-1+b1_i386 +libghc-test-framework-th-dev_0.2.2-5_i386 +libghc-test-framework-th-prime-dev_0.0.5-1_i386 +libghc-testpack-dev_2.1.1-1+b2_i386 +libghc-texmath-dev_0.6.0.6-1+b2_i386 +libghc-text-dev_0.11.2.0-1_i386 +libghc-text-icu-dev_0.6.3.4-2+b2_i386 +libghc-tinyurl-dev_0.1.0-2+b3_i386 +libghc-tls-dev_0.9.5-1+b4_i386 +libghc-tls-extra-dev_0.4.6.1-2_i386 +libghc-tokyocabinet-dev_0.0.5-5+b3_i386 +libghc-transformers-base-dev_0.4.1-2+b2_i386 +libghc-transformers-dev_0.3.0.0-1_i386 +libghc-type-level-dev_0.2.4-5_i386 +libghc-uniplate-dev_1.6.7-1+b2_i386 +libghc-unix-bytestring-dev_0.3.5-2+b1_i386 +libghc-unix-compat-dev_0.3.0.1-1+b1_i386 +libghc-unixutils-dev_1.50-1+b1_i386 +libghc-unlambda-dev_0.1-2+b2_i386 +libghc-unordered-containers-dev_0.2.1.0-1_i386 +libghc-uri-dev_0.1.6-1+b2_i386 +libghc-url-dev_2.1.2-4+b1_i386 +libghc-utf8-light-dev_0.4.0.1-2+b1_i386 +libghc-utf8-string-dev_0.3.7-1+b1_i386 +libghc-utility-ht-dev_0.0.5.1-3+b1_i386 +libghc-uuagc-cabal-dev_1.0.2.0-1+b1_i386 +libghc-uuid-dev_1.2.3-2+b4_i386 +libghc-uulib-dev_0.9.14-2_i386 +libghc-vault-dev_0.2.0.0-1+b2_i386 +libghc-vector-algorithms-dev_0.5.4-1+b2_i386 +libghc-vector-dev_0.9.1-2+b1_i386 +libghc-vector-space-dev_0.8.1-1_i386 +libghc-vector-space-points-dev_0.1.1.0-1+b1_i386 +libghc-void-dev_0.5.5.1-2+b1_i386 +libghc-vte-dev_0.12.1-1+b3_i386 +libghc-vty-dev_4.7.0.14-1+b1_i386 +libghc-wai-app-file-cgi-dev_0.5.8-1+b4_i386 +libghc-wai-app-static-dev_1.2.0.3-1+b3_i386 +libghc-wai-dev_1.2.0.2-1+b2_i386 +libghc-wai-extra-dev_1.2.0.4-1_i386 +libghc-wai-logger-dev_0.1.4-1+b6_i386 +libghc-wai-logger-prefork-dev_0.1.3-1+b6_i386 +libghc-wai-test-dev_1.2.0.2-1_i386 +libghc-warp-dev_1.2.1.1-1_i386 +libghc-warp-tls-dev_1.2.0.4-1+b4_i386 +libghc-web-routes-dev_0.25.3-2+b3_i386 +libghc-webkit-dev_0.12.3-2+b1_i386 +libghc-weighted-regexp-dev_0.3.1.1-2+b1_i386 +libghc-x11-dev_1.5.0.1-1+b2_i386 +libghc-x11-xft-dev_0.3.1-1+b3_i386 +libghc-xdg-basedir-dev_0.2.1-2+b1_i386 +libghc-xhtml-dev_3000.2.1-1_i386 +libghc-xml-conduit-dev_0.7.0.2-1_i386 +libghc-xml-dev_1.3.12-1+b2_i386 +libghc-xml-types-dev_0.3.1-2+b2_i386 +libghc-xml2html-dev_0.1.2.3-1_i386 +libghc-xmonad-contrib-dev_0.10-4~deb7u1_i386 +libghc-xmonad-dev_0.10-4+b2_i386 +libghc-xss-sanitize-dev_0.3.2-1+b1_i386 +libghc-yaml-dev_0.7.0.2-1+b2_i386 +libghc-yaml-light-dev_0.1.4-2+b2_i386 +libghc-yesod-auth-dev_1.0.2.1-2+b2_i386 +libghc-yesod-core-dev_1.0.1.2-1+b3_i386 +libghc-yesod-default-dev_1.0.1.1-1+b1_i386 +libghc-yesod-dev_1.0.1.6-2+b3_i386 +libghc-yesod-form-dev_1.0.0.4-1+b1_i386 +libghc-yesod-json-dev_1.0.0.1-1+b3_i386 +libghc-yesod-markdown-dev_0.4.0-1+b3_i386 +libghc-yesod-persistent-dev_1.0.0.1-1+b1_i386 +libghc-yesod-routes-dev_1.0.1.2-1_i386 +libghc-yesod-static-dev_1.0.0.2-1+b3_i386 +libghc-yesod-test-dev_0.2.0.6-1_i386 +libghc-zip-archive-dev_0.1.1.7-3+b2_i386 +libghc-zlib-bindings-dev_0.1.0.1-1_i386 +libghc-zlib-conduit-dev_0.4.0.1-1_i386 +libghc-zlib-dev_0.5.3.3-1+b1_i386 +libghc-zlib-enum-dev_0.2.2.1-1+b1_i386 +libghc6-agda-dev_1:8_all +libghc6-alut-dev_1:8_all +libghc6-arrows-dev_1:8_all +libghc6-binary-dev_1:8_all +libghc6-binary-shared-dev_1:8_all +libghc6-bzlib-dev_1:8_all +libghc6-cairo-dev_1:8_all +libghc6-cautious-file-dev_1:8_all +libghc6-cgi-dev_1:8_all +libghc6-colour-dev_1:8_all +libghc6-configfile-dev_1:8_all +libghc6-convertible-dev_1:8_all +libghc6-cpphs-dev_1:8_all +libghc6-criterion-dev_1:8_all +libghc6-csv-dev_1:8_all +libghc6-curl-dev_1:8_all +libghc6-data-accessor-dev_1:8_all +libghc6-dataenc-dev_1:8_all +libghc6-datetime-dev_1:8_all +libghc6-debian-dev_1:8_all +libghc6-deepseq-dev_1:8_all +libghc6-diagrams-dev_1:8_all +libghc6-diff-dev_1:8_all +libghc6-digest-dev_1:8_all +libghc6-edison-api-dev_1:8_all +libghc6-edison-core-dev_1:8_all +libghc6-editline-dev_1:8_all +libghc6-erf-dev_1:8_all +libghc6-event-list-dev_1:8_all +libghc6-explicit-exception-dev_1:8_all +libghc6-fastcgi-dev_1:8_all +libghc6-feed-dev_1:8_all +libghc6-fgl-dev_1:8_all +libghc6-filemanip-dev_1:8_all +libghc6-filestore-dev_1:8_all +libghc6-ftphs-dev_1:8_all +libghc6-gconf-dev_1:8_all +libghc6-ghc-events-dev_1:8_all +libghc6-ghc-mtl-dev_1:8_all +libghc6-ghc-paths-dev_1:8_all +libghc6-gio-dev_1:8_all +libghc6-gitit-dev_1:8_all +libghc6-glade-dev_1:8_all +libghc6-glfw-dev_1:8_all +libghc6-glib-dev_1:8_all +libghc6-glut-dev_1:8_all +libghc6-gstreamer-dev_1:8_all +libghc6-gtk-dev_1:8_all +libghc6-gtkglext-dev_1:8_all +libghc6-gtksourceview2-dev_1:8_all +libghc6-haddock-dev_1:8_all +libghc6-happstack-dev_1:8_all +libghc6-happstack-server-dev_1:8_all +libghc6-harp-dev_1:8_all +libghc6-hashed-storage-dev_1:8_all +libghc6-haskeline-dev_1:8_all +libghc6-haskell-lexer-dev_1:8_all +libghc6-haskell-src-dev_1:8_all +libghc6-haskelldb-dev_1:8_all +libghc6-haskelldb-hdbc-dev_1:8_all +libghc6-haskelldb-hdbc-odbc-dev_1:8_all +libghc6-haskelldb-hdbc-postgresql-dev_1:8_all +libghc6-haskelldb-hdbc-sqlite3-dev_1:8_all +libghc6-haskore-dev_1:8_all +libghc6-haxml-dev_1:8_all +libghc6-haxr-dev_1:8_all +libghc6-hdbc-dev_1:8_all +libghc6-hdbc-odbc-dev_1:8_all +libghc6-hdbc-postgresql-dev_1:8_all +libghc6-hdbc-sqlite3-dev_1:8_all +libghc6-highlighting-kate-dev_1:8_all +libghc6-hint-dev_1:8_all +libghc6-hjavascript-dev_1:8_all +libghc6-hjscript-dev_1:8_all +libghc6-hoauth-dev_1:8_all +libghc6-hscolour-dev_1:8_all +libghc6-hscurses-dev_1:8_all +libghc6-hsemail-dev_1:8_all +libghc6-hsh-dev_1:8_all +libghc6-hslogger-dev_1:8_all +libghc6-hsp-dev_1:8_all +libghc6-hsql-dev_1:8_all +libghc6-hsql-mysql-dev_1:8_all +libghc6-hsql-odbc-dev_1:8_all +libghc6-hsql-postgresql-dev_1:8_all +libghc6-hsql-sqlite3-dev_1:8_all +libghc6-hstringtemplate-dev_1:8_all +libghc6-hsx-dev_1:8_all +libghc6-html-dev_1:8_all +libghc6-http-dev_1:8_all +libghc6-hunit-dev_1:8_all +libghc6-hxt-dev_1:8_all +libghc6-ifelse-dev_1:8_all +libghc6-irc-dev_1:8_all +libghc6-json-dev_1:8_all +libghc6-language-c-dev_1:8_all +libghc6-lazysmallcheck-dev_1:8_all +libghc6-ldap-dev_1:8_all +libghc6-leksah-server-dev_1:8_all +libghc6-llvm-dev_1:8_all +libghc6-ltk-dev_1:8_all +libghc6-magic-dev_1:8_all +libghc6-markov-chain-dev_1:8_all +libghc6-maybet-dev_1:8_all +libghc6-midi-dev_1:8_all +libghc6-missingh-dev_1:8_all +libghc6-mmap-dev_1:8_all +libghc6-monadcatchio-mtl-dev_1:8_all +libghc6-monoid-transformer-dev_1:8_all +libghc6-mtl-dev_1:8_all +libghc6-mwc-random-dev_1:8_all +libghc6-network-dev_1:8_all +libghc6-non-negative-dev_1:8_all +libghc6-openal-dev_1:8_all +libghc6-opengl-dev_1:8_all +libghc6-pandoc-dev_1:8_all +libghc6-pango-dev_1:8_all +libghc6-parallel-dev_1:8_all +libghc6-parsec2-dev_1:8_all +libghc6-parsec3-dev_1:8_all +libghc6-pcre-light-dev_1:8_all +libghc6-polyparse-dev_1:8_all +libghc6-pretty-show-dev_1:8_all +libghc6-primitive-dev_1:8_all +libghc6-quickcheck1-dev_1:8_all +libghc6-quickcheck2-dev_1:8_all +libghc6-recaptcha-dev_1:8_all +libghc6-regex-base-dev_1:8_all +libghc6-regex-compat-dev_1:8_all +libghc6-regex-posix-dev_1:8_all +libghc6-regex-tdfa-dev_1:8_all +libghc6-regex-tdfa-utf8-dev_1:8_all +libghc6-safe-dev_1:8_all +libghc6-sdl-dev_1:8_all +libghc6-sdl-gfx-dev_1:8_all +libghc6-sdl-image-dev_1:8_all +libghc6-sdl-mixer-dev_1:8_all +libghc6-sdl-ttf-dev_1:8_all +libghc6-sendfile-dev_1:8_all +libghc6-sha-dev_1:8_all +libghc6-smtpclient-dev_1:8_all +libghc6-split-dev_1:8_all +libghc6-src-exts-dev_1:8_all +libghc6-statistics-dev_1:8_all +libghc6-stm-dev_1:8_all +libghc6-stream-dev_1:8_all +libghc6-strict-concurrency-dev_1:8_all +libghc6-svgcairo-dev_1:8_all +libghc6-syb-with-class-dev_1:8_all +libghc6-syb-with-class-instances-text-dev_1:8_all +libghc6-tagsoup-dev_1:8_all +libghc6-tar-dev_1:8_all +libghc6-terminfo-dev_1:8_all +libghc6-testpack-dev_1:8_all +libghc6-texmath-dev_1:8_all +libghc6-text-dev_1:8_all +libghc6-tokyocabinet-dev_1:8_all +libghc6-transformers-dev_1:8_all +libghc6-type-level-dev_1:8_all +libghc6-uniplate-dev_1:8_all +libghc6-unix-compat-dev_1:8_all +libghc6-unixutils-dev_1:8_all +libghc6-url-dev_1:8_all +libghc6-utility-ht-dev_1:8_all +libghc6-uulib-dev_1:8_all +libghc6-vector-algorithms-dev_1:8_all +libghc6-vector-dev_1:8_all +libghc6-vte-dev_1:8_all +libghc6-vty-dev_1:8_all +libghc6-webkit-dev_1:8_all +libghc6-x11-dev_1:8_all +libghc6-x11-xft-dev_1:8_all +libghc6-xhtml-dev_1:8_all +libghc6-xml-dev_1:8_all +libghc6-xmonad-contrib-dev_1:8_all +libghc6-xmonad-dev_1:8_all +libghc6-zip-archive-dev_1:8_all +libghc6-zlib-dev_1:8_all +libghemical-dev_3.0.0-2_i386 +libgif-dev_4.1.6-10_i386 +libgiftiio-dev_1.0.9-1_i386 +libgig-dev_3.3.0-2_i386 +libgii1-dev_1:1.0.2-4.1_i386 +libgimp2.0-dev_2.8.2-2+deb7u1_i386 +libginac-dev_1.6.2-1_i386 +libginspx-dev_20050529-3.1_i386 +libgio2.0-cil-dev_2.22.3-2_all +libgirara-dev_0.1.2-3_i386 +libgirepository1.0-dev_1.32.1-1_i386 +libgjs-dev_1.32.0-5_i386 +libgkeyfile-cil-dev_0.1-4_all +libgksu2-dev_2.0.13~pre1-6_i386 +libgl1-mesa-dev_8.0.5-4+deb7u2_i386 +libgl1-mesa-swx11-dev_8.0.5-4+deb7u2_i386 +libgl2ps-dev_1.3.6-1_i386 +libglade2-dev_1:2.6.4-1_i386 +libglade2.0-cil-dev_2.12.10-5_i386 +libglademm-2.4-dev_2.6.7-2_i386 +libgladeui-1-dev_3.6.7-2.1_i386 +libgladeui-dev_3.12.1-1_i386 +libglbsp-dev_2.24-1_i386 +libglc-dev_0.7.2-5+b1_i386 +libgle3-dev_3.1.0-7_i386 +libgles1-mesa-dev_8.0.5-4+deb7u2_i386 +libgles2-mesa-dev_8.0.5-4+deb7u2_i386 +libglew-dev_1.7.0-3_i386 +libglewmx-dev_1.7.0-3_i386 +libglfw-dev_2.7.2-1_i386 +libglib2.0-cil-dev_2.12.10-5_i386 +libglib2.0-dev_2.33.12+really2.32.4-5_i386 +libglibmm-2.4-dev_2.32.1-1_i386 +libglide2-dev_2002.04.10ds1-7_i386 +libglide3-dev_2002.04.10ds1-7_i386 +libglm-dev_0.9.3.3+dfsg-0.1_all +libglobus-authz-callout-error-dev_2.2-1_i386 +libglobus-authz-dev_2.2-1_i386 +libglobus-callout-dev_2.2-1_i386 +libglobus-common-dev_14.7-2_i386 +libglobus-ftp-client-dev_7.3-1_i386 +libglobus-ftp-control-dev_4.4-1_i386 +libglobus-gass-cache-dev_8.1-2_i386 +libglobus-gass-copy-dev_8.4-1_i386 +libglobus-gass-server-ez-dev_4.3-1_i386 +libglobus-gass-transfer-dev_7.2-1_i386 +libglobus-gfork-dev_3.2-1_i386 +libglobus-gram-client-dev_12.4-1_i386 +libglobus-gram-job-manager-callout-error-dev_2.1-2_i386 +libglobus-gram-protocol-dev_11.3-1_i386 +libglobus-gridftp-server-control-dev_2.5-2_i386 +libglobus-gridftp-server-dev_6.10-2_i386 +libglobus-gridmap-callout-error-dev_1.2-2_i386 +libglobus-gsi-callback-dev_4.2-1_i386 +libglobus-gsi-cert-utils-dev_8.3-1_i386 +libglobus-gsi-credential-dev_5.3-1_i386 +libglobus-gsi-openssl-error-dev_2.1-2_i386 +libglobus-gsi-proxy-core-dev_6.2-1_i386 +libglobus-gsi-proxy-ssl-dev_4.1-2_i386 +libglobus-gsi-sysconfig-dev_5.2-1_i386 +libglobus-gss-assist-dev_8.5-1_i386 +libglobus-gssapi-error-dev_4.1-2_i386 +libglobus-gssapi-gsi-dev_10.6-1_i386 +libglobus-io-dev_9.3-1_i386 +libglobus-openssl-module-dev_3.2-1_i386 +libglobus-rls-client-dev_5.2-8_i386 +libglobus-rsl-dev_9.1-2_i386 +libglobus-scheduler-event-generator-dev_4.6-1_i386 +libglobus-usage-dev_3.1-2_i386 +libglobus-xio-dev_3.3-1_i386 +libglobus-xio-gsi-driver-dev_2.3-1_i386 +libglobus-xio-pipe-driver-dev_2.2-1_i386 +libglobus-xio-popen-driver-dev_2.3-1_i386 +libgloox-dev_1.0-1.1_i386 +libglpk-dev_4.45-1_i386 +libglrr-glib-dev_20050529-3.1_i386 +libglrr-gobject-dev_20050529-3.1_i386 +libglrr-gtk-dev_20050529-3.1_i386 +libglrr-widgets-dev_20050529-3.1_i386 +libglu1-mesa-dev_8.0.5-4+deb7u2_i386 +libglui-dev_2.36-4_i386 +libglw1-mesa-dev_8.0.0-1_i386 +libgme-dev_0.5.5-2_i386 +libgmerlin-avdec-dev_1.2.0~dfsg-1+b1_i386 +libgmerlin-dev_1.2.0~dfsg+1-1_i386 +libgmime-2.6-dev_2.6.10-1_i386 +libgmime2.6-cil-dev_2.6.10-1_all +libgmlib-dev_1.0.6-1_i386 +libgmm++-dev_4.1.1+dfsg1-11_all +libgmp-dev_2:5.0.5+dfsg-2_i386 +libgmp-ocaml-dev_20021123-17+b3_i386 +libgmp3-dev_2:5.0.5+dfsg-2_i386 +libgmpada3-dev_0.0.20120331-1_i386 +libgmt-dev_4.5.7-2_i386 +libgmtk-dev_1.0.6-1_i386 +libgnadecommon2-dev_1.6.2-9_i386 +libgnadeodbc2-dev_1.6.2-9_i386 +libgnadesqlite3-2-dev_1.6.2-9_i386 +libgnatprj4.6-dev_4.6.3-8_i386 +libgnatvsn4.6-dev_4.6.3-8_i386 +libgnelib-dev_0.75+svn20091130-1+b1_i386 +libgnet-dev_2.0.8-2.2_i386 +libgnokii-dev_0.6.30+dfsg-1+b1_i386 +libgnome-bluetooth-dev_3.4.2-1_i386 +libgnome-desktop-3-dev_3.4.2-1_i386 +libgnome-desktop-dev_2.32.1-2_i386 +libgnome-keyring-dev_3.4.1-1_i386 +libgnome-keyring1.0-cil-dev_1.0.0-4_i386 +libgnome-mag-dev_1:0.16.3-1_i386 +libgnome-media-profiles-dev_3.0.0-1_i386 +libgnome-menu-3-dev_3.4.2-5_i386 +libgnome-menu-dev_3.0.1-4_i386 +libgnome-speech-dev_1:0.4.25-5_i386 +libgnome-vfs2.0-cil-dev_2.24.2-3_all +libgnome-vfsmm-2.6-dev_2.26.0-1_i386 +libgnome2-dev_2.32.1-3_i386 +libgnome2.0-cil-dev_2.24.2-3_i386 +libgnomeada2.24.1-dev_2.24.1-7_i386 +libgnomecanvas2-dev_2.30.3-1.2_i386 +libgnomecanvasmm-2.6-dev_2.26.0-1_i386 +libgnomecups1.0-dev_0.2.3-5_i386 +libgnomedesktop2.0-cil-dev_2.26.0-8_all +libgnomekbd-dev_3.4.0.2-1_i386 +libgnomemm-2.6-dev_2.30.0-1_i386 +libgnomeprint2.2-dev_2.18.8-3_i386 +libgnomeprintui2.2-dev_2.18.6-3_i386 +libgnomeui-dev_2.24.5-2_i386 +libgnomeuimm-2.6-dev_2.28.0-1_i386 +libgnomevfs2-dev_1:2.24.4-2_i386 +libgnuift0-dev_0.1.14-12_i386 +libgnuplot-ocaml-dev_0.8.3-3_i386 +libgnustep-base-dev_1.22.1-4_i386 +libgnustep-dl2-dev_0.12.0-9+nmu1_i386 +libgnustep-gui-dev_0.20.0-3_i386 +libgnutls-dev_2.12.20-8+deb7u1_i386 +libgoa-1.0-dev_3.4.2-2_i386 +libgoffice-0.8-dev_0.8.17-1.2_i386 +libgofigure-dev_0.9.0-1+b2_i386 +libgoocanvas-dev_0.15-1_i386 +libgoocanvasmm-dev_0.15.4-1_i386 +libgoogle-perftools-dev_2.0-2_i386 +libgooglepinyin0-dev_0.1.2-1_i386 +libgpac-dev_0.5.0~dfsg0-1_i386 +libgpds-dev_1.5.1-6_i386 +libgpelaunch-dev_0.14-6_i386 +libgpepimc-dev_0.9-4_i386 +libgpeschedule-dev_0.17-4_i386 +libgpevtype-dev_0.50-6_i386 +libgpewidget-dev_0.117-6_i386 +libgpg-error-dev_1.10-3.1_i386 +libgpgme11-dev_1.2.0-1.4_i386 +libgphoto2-2-dev_2.4.14-2_i386 +libgpiv3-dev_0.6.1-4_i386 +libgpm-dev_1.20.4-6_i386 +libgpod-cil-dev_0.8.2-7_i386 +libgpod-dev_0.8.2-7_i386 +libgpod-nogtk-dev_0.8.2-7_i386 +libgportugol-dev_1.1-2_i386 +libgps-dev_3.6-4+deb7u1_i386 +libgraflib1-dev_20061220+dfsg3-2_i386 +libgrafx11-1-dev_20061220+dfsg3-2_i386 +libgrantlee-dev_0.1.4-1_i386 +libgraphicsmagick++1-dev_1.3.16-1.1_i386 +libgraphicsmagick1-dev_1.3.16-1.1_i386 +libgraphite-dev_1:2.3.1-0.2_i386 +libgraphite2-dev_1.1.3-1_i386 +libgraphviz-dev_2.26.3-14+deb7u1_i386 +libgretl1-dev_1.9.9-1_i386 +libgrib-api-dev_1.9.16-2+b1_i386 +libgrib2c-dev_1.2.2-2+b1_i386 +libgridsite-dev_1.7.16-1_i386 +libgrilo-0.1-dev_0.1.19-1_i386 +libgringotts-dev_1.2.10~pre3-1_i386 +libgrits-dev_0.7-1_i386 +libgrok-dev_1.20110708.1-4_i386 +libgrss-dev_0.5.0-1_i386 +libgs-dev_9.05~dfsg-6.3+deb7u1_i386 +libgsasl7-dev_1.8.0-2_i386 +libgsecuredelete-dev_0.2-1_i386 +libgsf-1-dev_1.14.21-2.1_i386 +libgsf-gnome-1-dev_1.14.21-2.1_i386 +libgsl0-dev_1.15+dfsg.2-2_i386 +libgsm0710-dev_1.2.2-2_i386 +libgsm0710mux-dev_0.11.2-1.1_i386 +libgsm1-dev_1.0.13-4_i386 +libgsmme-dev_1.10-13.2_i386 +libgsnmp0-dev_0.3.0-1.1_i386 +libgsql-dev_0.2.2-1.2+b1_i386 +libgss-dev_1.0.2-1_i386 +libgssdp-1.0-dev_0.12.2.1-2_i386 +libgssglue-dev_0.4-2_i386 +libgst-dev_3.2.4-2_i386 +libgstbuzztard-dev_0.5.0-2+deb7u1_i386 +libgstreamer-ocaml-dev_0.1.0-3+b1_i386 +libgstreamer-plugins-bad0.10-dev_0.10.23-7.1+deb7u1_i386 +libgstreamer-plugins-base0.10-dev_0.10.36-1.1_i386 +libgstreamer0.10-cil-dev_0.9.2-4_all +libgstreamer0.10-dev_0.10.36-1.2_i386 +libgstrtspserver-0.10-dev_0.10.8-3_i386 +libgtest-dev_1.6.0-2_i386 +libgtextutils-dev_0.6.2-1_i386 +libgtg-dev_0.2+dfsg-1_i386 +libgtk-3-dev_3.4.2-7_i386 +libgtk-sharp-beans2.0-cil-dev_2.14.1-3_all +libgtk-vnc-1.0-dev_0.5.0-3.1_i386 +libgtk-vnc-2.0-dev_0.5.0-3.1_i386 +libgtk2.0-cil-dev_2.12.10-5_i386 +libgtk2.0-dev_2.24.10-2_i386 +libgtkada2.24.1-dev_2.24.1-7_i386 +libgtkdatabox-0.9.1-1-dev_1:0.9.1.1-4_i386 +libgtkgl2.0-dev_2.0.1-2_i386 +libgtkglada2.24.1-dev_2.24.1-7_i386 +libgtkglarea-cil-dev_0.0.17-6_all +libgtkglext1-dev_1.2.0-2_i386 +libgtkglextmm-x11-1.2-dev_1.2.0-4.1_i386 +libgtkhex-3-dev_3.4.1-1_i386 +libgtkhotkey-dev_0.2.1-3_i386 +libgtkhtml-4.0-dev_4.4.4-1_i386 +libgtkhtml-editor-3.14-dev_3.32.2-2.1_i386 +libgtkhtml-editor-4.0-dev_4.4.4-1_i386 +libgtkhtml3.14-cil-dev_2.26.0-8_i386 +libgtkhtml3.14-dev_3.32.2-2.1_i386 +libgtkimageview-dev_1.6.4+dfsg-0.1_i386 +libgtkmathview-dev_0.8.0-8_i386 +libgtkmm-2.4-dev_1:2.24.2-1_i386 +libgtkmm-3.0-dev_3.4.2-1_i386 +libgtkpod-dev_2.1.2-1_i386 +libgtksourceview-3.0-dev_3.4.2-1_i386 +libgtksourceview2-cil-dev_2.26.0-8_i386 +libgtksourceview2.0-dev_2.10.4-1_i386 +libgtksourceviewmm-3.0-dev_3.2.0-1_i386 +libgtkspell-3-dev_3.0.0~hg20110814-1_i386 +libgtkspell-dev_2.0.16-1_i386 +libgtop2-dev_2.28.4-3_i386 +libgts-dev_0.7.6+darcs110121-1.1_i386 +libguac-dev_0.6.0-2_i386 +libgucharmap-2-90-dev_1:3.4.1.1-2.1_i386 +libgudev-1.0-dev_175-7.2_i386 +libgudev1.0-cil-dev_0.1-3_all +libguess-dev_1.1-1_i386 +libguestfs-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-gobject-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-ocaml-dev_1:1.18.1-1+deb7u3_i386 +libguichan-dev_0.8.2-10+b1_i386 +libgupnp-1.0-dev_0.18.4-1_i386 +libgupnp-av-1.0-dev_0.10.3-1_i386 +libgupnp-dlna-1.0-dev_0.6.6-1_i386 +libgupnp-igd-1.0-dev_0.2.1-2_i386 +libgusb-dev_0.1.3-5_i386 +libgutenprint-dev_5.2.9-1_i386 +libgutenprintui2-dev_5.2.9-1_i386 +libguytools2-dev_2.0.1-1.1_i386 +libgvnc-1.0-dev_0.5.0-3.1_i386 +libgweather-3-dev_3.4.1-1+build1_i386 +libgwenhywfar60-dev_4.3.3-1_i386 +libgwrap-runtime-dev_1.9.14-1.1_i386 +libgwyddion20-dev_2.28-2_i386 +libgxps-dev_0.2.2-2_i386 +libgyoto0-dev_0.0.3-5_i386 +libh323plus-dev_1.24.0~dfsg2-1_i386 +libhaildb-dev_2.3.2-1.2_i386 +libhal-dev_0.5.14-8_i386 +libhal-storage-dev_0.5.14-8_i386 +libhamlib++-dev_1.2.15.1-1_i386 +libhamlib-dev_1.2.15.1-1_i386 +libhandoff-dev_0.1-5_i386 +libhangul-dev_0.1.0-2_i386 +libharminv-dev_1.3.1-9_i386 +libhashkit-dev_1.0.8-1_i386 +libhawknl-dev_1.6.8+dfsg2-1_i386 +libhbaapi-dev_2.2.5-1_i386 +libhbalinux-dev_1.0.14-1_i386 +libhd-dev_16.0-2.2_i386 +libhdate-dev_1.6-1_i386 +libhdf4-alt-dev_4.2r4-13_i386 +libhdf4-dev_4.2r4-13_i386 +libhdf4g-dev_4.2r4-13_all +libhdf5-dev_1.8.8-9_i386 +libhdf5-mpi-dev_1.8.8-9_i386 +libhdf5-mpich2-dev_1.8.8-9_i386 +libhdf5-openmpi-dev_1.8.8-9_i386 +libhdf5-serial-dev_1.8.8-9_i386 +libhdfeos-dev_2.17v1.00.dfsg.1-3_i386 +libhdhomerun-dev_20120405-1_i386 +libhe5-hdfeos-dev_5.1.13.dfsg.1-3_i386 +libheartbeat2-dev_1:3.0.5-3_i386 +libhepmc-dev_2.06.09-1_i386 +libhepmcfio-dev_2.06.09-1_i386 +libhepmcinterface8-dev_8.1.65-1_i386 +libherwig59-2-dev_20061220+dfsg3-2_i386 +libhesiod-dev_3.0.2-21_i386 +libhfsp-dev_1.0.4-12_i386 +libhighgui-dev_2.3.1-11_i386 +libhippocanvas-dev_0.3.1-1.1_i386 +libhiredis-dev_0.10.1-7_i386 +libhivex-dev_1.3.6-2_i386 +libhivex-ocaml-dev_1.3.6-2_i386 +libhkl-dev_4.0.3-4_i386 +libhmsbeagle-dev_1.0-6_i386 +libhocr-dev_0.10.17-1+b2_i386 +libhpdf-dev_2.2.1-1_i386 +libhpmud-dev_3.12.6-3.1+deb7u1_i386 +libhsclient-dev_1.1.0-7-g1044a28-1_i386 +libhtmlcxx-dev_0.85-2_i386 +libhtp-dev_0.2.6-2_i386 +libhtsengine-dev_1.06-1_i386 +libhttp-ocaml-dev_0.1.5-1+b2_i386 +libhttrack-dev_3.46.1-1_i386 +libhunspell-dev_1.3.2-4_i386 +libhwloc-dev_1.4.1-4_i386 +libhx-dev_3.12.1-1_i386 +libhyantes-dev_1.3.0-1_i386 +libhyena-cil-dev_0.5-2_all +libhyphen-dev_2.8.3-2_i386 +libhypre-dev_2.8.0b-1_all +libhz-dev_0.3.16-3_i386 +libi2c-dev_3.1.0-2_all +libibcm-dev_1.0.4-1.1_i386 +libibcommon-dev_1.1.2-20090314-1_i386 +libibdm-dev_1.2-OFED-1.4.2-1.3_i386 +libibmad-dev_1.2.3-20090314-1.1_i386 +libibtk-dev_0.0.14-12_i386 +libibumad-dev_1.2.3-20090314-1.1_i386 +libibus-1.0-dev_1.4.1-9+deb7u1_i386 +libibus-qt-dev_1.3.1-2.1_i386 +libibverbs-dev_1.1.6-1_i386 +libical-dev_0.48-2_i386 +libicapapi-dev_1:0.1.6-1.1_i386 +libicc-dev_2.12+argyll1.4.0-8_i386 +libicc-utils-dev_1.6.4-1+b1_i386 +libice-dev_2:1.0.8-2_i386 +libicee-dev_1.2.0-6.1_i386 +libicns-dev_0.8.1-1_i386 +libiconv-hook-dev_0.0.20021209-10_i386 +libics-dev_1.5.2-3_i386 +libicu-dev_4.8.1.1-12+deb7u1_i386 +libid3-3.8.3-dev_3.8.3-15_i386 +libid3tag0-dev_0.15.1b-10_i386 +libident-dev_0.22-3_i386 +libidl-dev_0.8.14-0.2_i386 +libidn11-dev_1.25-2_i386 +libidn2-0-dev_0.8-2_i386 +libido-0.1-dev_0.3.4-1_i386 +libido3-0.1-dev_0.3.4-1_i386 +libidzebra-2.0-dev_2.0.44-3_i386 +libiec16022-dev_0.2.4-1_i386 +libiec61883-dev_1.2.0-0.1_i386 +libieee1284-3-dev_0.2.11-10_i386 +libifp-dev_1.0.0.2-5_i386 +libifstat-dev_1.1-8_i386 +libigraph0-dev_0.5.4-2_i386 +libigstk4-dev_4.4.0-2+b1_i386 +libijs-dev_0.35-8_i386 +libiksemel-dev_1.2-4_i386 +libilmbase-dev_1.0.1-4_i386 +libimdi-dev_1.4.0-8_i386 +libiml-dev_1.0.3-4.2_i386 +libimlib2-dev_1.4.5-1_i386 +libimobiledevice-dev_1.1.1-4_i386 +libindi-dev_0.9.1-2_i386 +libindicate-dev_0.6.92-1_i386 +libindicate-gtk-dev_0.6.92-1_i386 +libindicate-gtk0.1-cil-dev_0.6.92-1_all +libindicate-gtk3-dev_0.6.92-1_i386 +libindicate-qt-dev_0.2.5.91-5_i386 +libindicate0.1-cil-dev_0.6.92-1_all +libindicator-dev_0.5.0-1_i386 +libindicator-messages-status-provider-dev_0.6.0-1_i386 +libindicator3-dev_0.5.0-1_i386 +libindigo-dev_1.0.0-2_i386 +libinfinity-0.5-dev_0.5.2-6.1_i386 +libini-config-dev_0.1.3-2_i386 +libinifiles-ocaml-dev_1.2-2_i386 +libinnodb-dev_1.0.6.6750-1_i386 +libinotify-ocaml-dev_1.0-1+b3_i386 +libinotifytools0-dev_3.14-1_i386 +libinput-pad-dev_1.0.1-2_i386 +libinsighttoolkit3-dev_3.20.1+git20120521-3_i386 +libinstpatch-dev_1.0.0-3_i386 +libint-dev_1.1.4-1_i386 +libiodbc2-dev_3.52.7-2+deb7u1_i386 +libion-dev_3.0.1~dfsg1-1_i386 +libipa-hbac-dev_1.8.4-2_i386 +libipathverbs-dev_1.2-1_i386 +libipe-dev_7.1.2-1_i386 +libipmiconsole-dev_1.1.5-3_i386 +libipmidetect-dev_1.1.5-3_i386 +libipmimonitoring-dev_1.1.5-3_i386 +libipset-dev_6.12.1-1_i386 +libiptcdata0-dev_1.0.4-3_i386 +libircclient-dev_1.3+dfsg1-3_i386 +libirman-dev_0.4.4-2_i386 +libirrlicht-dev_1.7.3+dfsg1-4_i386 +libisajet758-3-dev_20061220+dfsg3-2_i386 +libiscsi-dev_1.4.0-3_i386 +libisl-dev_0.10-3_i386 +libiso9660-dev_0.83-4_i386 +libisoburn-dev_1.2.2-2_i386 +libisofs-dev_1.2.2-1_i386 +libitl-dev_0.7.0-3_i386 +libitl-gobject-dev_0.2-1_i386 +libitpp-dev_4.2-4_i386 +libitsol-dev_1.0.0-2_i386 +libivykis-dev_0.30.1-2_i386 +libiw-dev_30~pre9-8_i386 +libjack-dev_1:0.121.3+20120418git75e3e20b-2.1_i386 +libjack-jackd2-dev_1.9.8~dfsg.4+20120529git007cdc37-5_i386 +libjalali-dev_0.4.0-1.1_i386 +libjama-dev_1.2.4-2_all +libjana-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-ecal-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-gtk-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjansson-dev_2.3.1-2_i386 +libjasper-dev_1.900.1-13_i386 +libjaula-dev_1.4.0-3_i386 +libjavascriptcoregtk-1.0-dev_1.8.1-3.4_i386 +libjavascriptcoregtk-3.0-dev_1.8.1-3.4_i386 +libjbig-dev_2.0-2+deb7u1_i386 +libjbig2dec0-dev_0.11+20120125-1_i386 +libjconv-dev_2.8-6+b1_i386 +libjemalloc-dev_3.0.0-3_i386 +libjim-dev_0.73-3_i386 +libjpeg62-dev_6b1-3_i386 +libjpeg8-dev_8d-1_i386 +libjpgalleg4-dev_2:4.4.2-2.1_i386 +libjs-of-ocaml-dev_1.2-2_i386 +libjson-glib-dev_0.14.2-1_i386 +libjson-spirit-dev_4.04-1+b1_i386 +libjson-static-camlp4-dev_0.9.8-1+b5_i386 +libjson-wheel-ocaml-dev_1.0.6-2+b8_i386 +libjson0-dev_0.10-1.2_i386 +libjsoncpp-dev_0.6.0~rc2-3_i386 +libjte-dev_1.19-1_i386 +libjthread-dev_1.3.1-3_i386 +libjudy-dev_1.0.5-1_i386 +libjuman-dev_5.1-2.1_i386 +libk3b-dev_2.0.2-6_i386 +libkactivities-dev_4:4.8.4-1_i386 +libkakasi2-dev_2.3.5~pre1+cvs20071101-1_i386 +libkal-dev_0.9.0-1_i386 +libkarma-cil-dev_0.1.2-2.3_all +libkarma-dev_0.1.2-2.3_i386 +libkate-dev_0.4.1-1_i386 +libkaya-gd-dev_0.4.4-6_i386 +libkaya-gl-dev_0.4.4-6_i386 +libkaya-mysql-dev_0.4.4-6_i386 +libkaya-ncurses-dev_0.4.4-6_i386 +libkaya-ncursesw-dev_0.4.4-6_i386 +libkaya-pgsql-dev_0.4.4-6_i386 +libkaya-sdl-dev_0.4.4-6_i386 +libkaya-sqlite3-dev_0.4.4-6_i386 +libkcddb-dev_4:4.8.4-2_i386 +libkdcraw-dev_4:4.8.4-1_i386 +libkdeedu-dev_4:4.8.4-1_i386 +libkdegames-dev_4:4.8.4-3_i386 +libkdtree++-dev_0.7.0-2_all +libkernlib1-dev_20061220+dfsg3-2_i386 +libkexiv2-dev_4:4.8.4-1_i386 +libkeybinder-dev_0.2.2-4_i386 +libkeyutils-dev_1.5.5-3_i386 +libkibi-dev_0.1-1_i386 +libkipi-dev_4:4.8.4-1_i386 +libkiten-dev_4:4.8.4-1_i386 +libklatexformula3-dev_3.2.6-1_i386 +libklibc-dev_2.0.1-3.1_i386 +libkmfl-dev_0.9.8-1_i386 +libkmflcomp-dev_0.9.8-1_i386 +libkml-dev_1.3.0~r863-4.1_i386 +libkmod-dev_9-3_i386 +libkokyu-dev_6.0.3+dfsg-0.1_i386 +libkonq5-dev_4:4.8.4-2_i386 +libkonqsidebarplugin-dev_4:4.8.4-2_i386 +libkopete-dev_4:4.8.4-1+b1_i386 +libkosd2-dev_0.8.1-1_i386 +libkpathsea-dev_2012.20120628-4_i386 +libkqueue-dev_1.0.4-2_i386 +libkrb5-dev_1.10.1+dfsg-5+deb7u1_i386 +libksane-dev_4:4.8.4-1_i386 +libksba-dev_1.2.0-2_i386 +libktoblzcheck1-dev_1.39-1_i386 +libktorrent-dev_1.2.1-1_i386 +libktpcommoninternalsprivate-dev_0.4.0-1_i386 +libkvutils-dev_2.9.0-1_i386 +libkvutils2.2-dev_2.9.0-1_all +libkwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libkwwidgets1-dev_1.0.0~cvs20100930-8_i386 +libkxl0-dev_1.1.7-16_i386 +liblablgl-ocaml-dev_1.04-5+b3_i386 +liblablgtk-extras-ocaml-dev_1.0-1+b2_i386 +liblablgtk2-gl-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-gnome-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtkmathview-ocaml-dev_0.7.8-6+b1_i386 +liblablgtksourceview2-ocaml-dev_2.14.2+dfsg-3_i386 +libladr-dev_0.0.200902a-2.1_i386 +libladspa-ocaml-dev_0.1.4-1+b1_i386 +liblapack-dev_3.4.1+dfsg-1+deb70u1_i386 +liblapacke-dev_3.4.1+dfsg-1+deb70u1_i386 +liblas-dev_1.2.1-5+b1_i386 +liblash-compat-dev_1+dfsg0-3_all +liblasi-dev_1.1.0-1_i386 +liblasso3-dev_2.3.6-2_i386 +liblastfm-dev_0.4.0~git20090710-2_i386 +liblastfm-ocaml-dev_0.3.0-2+b6_i386 +liblcgdm-dev_1.8.2-1+b2_i386 +liblcms1-dev_1.19.dfsg-1.2_i386 +liblcms2-dev_2.2+git20110628-2.2+deb7u1_i386 +libldap-ocaml-dev_2.1.8-8+b9_i386 +libldap2-dev_2.4.31-1+nmu2_i386 +libldb-dev_1:1.1.6-1_i386 +libldns-dev_1.6.13-1_i386 +libledit-ocaml-dev_2.03-1+b2_i386 +liblensfun-dev_0.2.5-2_i386 +libleptonica-dev_1.69-3.1_i386 +libleveldb-dev_0+20120530.gitdd0d562-1_i386 +liblfc-dev_1.8.2-1+b2_i386 +liblhapdf-dev_5.8.7+repack-1_i386 +liblhasa-dev_0.0.7-2_i386 +liblicense-dev_0.8.1-3_i386 +liblightdm-gobject-dev_1.2.2-4_i386 +liblightdm-qt-dev_1.2.2-4_i386 +liblilv-dev_0.14.2~dfsg0-4_i386 +liblinear-dev_1.8+dfsg-1_i386 +liblinebreak2-dev_2.1-1_i386 +liblink-grammar4-dev_4.7.4-2_i386 +liblinphone-dev_3.5.2-10_i386 +liblip-dev_2.0.0-1.1_i386 +liblircclient-dev_0.9.0~pre1-1_i386 +liblistaller-glib-dev_0.5.5-2_i386 +liblivemedia-dev_2012.05.17-1_i386 +libllvm-2.9-ocaml-dev_2.9+dfsg-7_i386 +libllvm-3.0-ocaml-dev_3.0-10_i386 +libllvm-3.1-ocaml-dev_3.1-1_i386 +libllvm-ocaml-dev_1:3.0-14+nmu2_i386 +liblo-dev_0.26~repack-7_i386 +liblo-ocaml-dev_0.1.0-1+b1_i386 +liblo10k1-dev_1.0.25-2_i386 +libloadpng4-dev_2:4.4.2-2.1_i386 +liblockdev1-dev_1.0.3-1.5_i386 +liblockfile-dev_1.09-5_i386 +liblodo3.0-dev_3.0.2+dfsg-4+b1_i386 +liblog4ada2-dev_1.2-3_i386 +liblog4c-dev_1.2.1-3_i386 +liblog4cplus-dev_1.0.4-1_i386 +liblog4cpp5-dev_1.0-4_i386 +liblog4cxx10-dev_0.10.0-1.2_i386 +liblog4net-cil-dev_1.2.10+dfsg-6_all +liblog4shib-dev_1.0.4-1_i386 +liblog4tango4-dev_7.2.6+dfsg-14_i386 +liblogforwarderutils2-dev_2.7-1_i386 +liblognorm-dev_0.3.4-1_i386 +liblogservicecomponentbase2-dev_2.7-1_i386 +liblogservicetoolbase2-dev_2.7-1_i386 +liblogsys-dev_1.4.2-3_i386 +liblogthread-dev_3.0.12-3.2+deb7u2_i386 +libloki-dev_0.1.7-3_i386 +libloudmouth1-dev_1.4.3-9_i386 +liblouis-dev_2.4.1-1_i386 +liblouisutdml-dev_2.2.0-1_i386 +liblouisxml-dev_2.4.0-3_i386 +liblowpan-dev_0.2.2-2.1_all +liblpsolve55-dev_5.5.0.13-7_i386 +liblqr-1-0-dev_0.4.1-2_i386 +liblrdf0-dev_0.4.0-5_i386 +liblrm2-dev_1.0.9+hg2665-1_i386 +liblrs-dev_0.42c-1+b1_i386 +liblscp-dev_0.5.6-6_i386 +libltcsmpte-dev_0.4.4-1_i386 +libltdl-dev_2.4.2-1.1_i386 +liblttctl-dev_0.89-05122011-1_i386 +liblttd-dev_0.89-05122011-1_i386 +liblttng-ust-dev_2.0.4-1_i386 +liblttoolbox3-3.1-0-dev_3.1.0-1.1_i386 +liblttvtraceread-2.6-dev_0.12.38-21032011-1+b1_i386 +liblua5.1-0-dev_5.1.5-4_i386 +liblua5.1-apr-dev_0.23.2-1_all +liblua5.1-bitop-dev_1.0.2-1_all +liblua5.1-cgi-dev_5.1.4+dfsg-2_all +liblua5.1-copas-dev_1.1.6-5_all +liblua5.1-curl-dev_0.3.0-7_all +liblua5.1-cyrussasl-dev_1.0.0-4_all +liblua5.1-event-dev_0.4.1-2_all +liblua5.1-expat-dev_1.2.0-5+deb7u1_all +liblua5.1-filesystem-dev_1.5.0+16+g84f1af5-1_all +liblua5.1-leg-dev_0.1.2-8_all +liblua5.1-logging-dev_1.2.0-1_all +liblua5.1-lpeg-dev_0.10.2-5_all +liblua5.1-md5-dev_1.1.2-6_all +liblua5.1-oocairo-dev_1.4-1.2_i386 +liblua5.1-oopango-dev_1.1-1_i386 +liblua5.1-orbit-dev_2.2.0+dfsg1-1_all +liblua5.1-posix-dev_5.1.19-2_all +liblua5.1-rex-onig-dev_2.6.0-2_all +liblua5.1-rex-pcre-dev_2.6.0-2_all +liblua5.1-rex-posix-dev_2.6.0-2_all +liblua5.1-rings-dev_1.2.3-1_all +liblua5.1-rrd-dev_1.4.7-2_i386 +liblua5.1-sec-dev_0.4.1-1_all +liblua5.1-soap-dev_3.0-3_all +liblua5.1-socket-dev_2.0.2-8_all +liblua5.1-sql-mysql-dev_2.3.0-1+build0_all +liblua5.1-sql-postgres-dev_2.3.0-1+build0_all +liblua5.1-sql-sqlite3-dev_2.3.0-1+build0_all +liblua5.1-svn-dev_0.4.0-7_all +liblua5.1-wsapi-fcgi-dev_1.5-3_all +liblua5.1-xmlrpc-dev_1.2.1-5_all +liblua5.1-zip-dev_1.2.3-11_all +liblua5.2-dev_5.2.1-3_i386 +liblua50-dev_5.0.3-6_i386 +libluabind-dev_0.9.1+dfsg-5_i386 +liblualib50-dev_5.0.3-6_i386 +liblunar-1-dev_2.0.1-2.2_i386 +liblunar-date-dev_2.4.0-1_i386 +liblv2dynparam1-dev_2-5_i386 +liblvm2-dev_2.02.95-8_i386 +liblwipv6-dev_1.5a-2_i386 +liblwt-glib-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ssl-ocaml-dev_2.3.2-1+b3_i386 +liblz-dev_1.3-2_i386 +liblzma-dev_5.1.1alpha+20120614-2_i386 +liblzo2-dev_2.06-1_i386 +libm17n-dev_1.6.3-2_i386 +libm17n-im-config-dev_0.9.0-3_i386 +libm4ri-dev_0.0.20080521-2_i386 +libmaa-dev_1.3.1-1_i386 +libmad-ocaml-dev_0.4.4-1+b1_i386 +libmad0-dev_0.15.1b-7_i386 +libmadlib-dev_1.3.0-2.1_i386 +libmagic-dev_5.11-2+deb7u3_i386 +libmagic-ocaml-dev_0.7.3-5+b3_i386 +libmagick++-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickcore-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickwand-dev_8:6.7.7.10-5+deb7u3_i386 +libmagics++-dev_2.14.11-4_i386 +libmailutils-dev_1:2.99.97-3_i386 +libmalaga-dev_7.12-4_i386 +libmaloc-dev_0.2-2.3_i386 +libmapi-dev_1:1.0-3_i386 +libmapiadmin-dev_1:1.0-3_i386 +libmapipp-dev_1:1.0-3_i386 +libmapiproxy-dev_1:1.0-3_i386 +libmapistore-dev_1:1.0-3_i386 +libmapnik-dev_2.0.0+ds1-3_all +libmapnik2-dev_2.0.0+ds1-3+b4_i386 +libmarble-dev_4:4.8.4-3_i386 +libmarkdown2-dev_2.1.3-3_i386 +libmatchbox-dev_1.9-osso8-3_i386 +libmath++-dev_0.0.4-4_i386 +libmatheval-dev_1.1.8-1_i386 +libmathlib2-dev_20061220+dfsg3-2_i386 +libmatio-dev_1.3.4-4_i386 +libmatrixssl1.8-dev_1.8.8-1_i386 +libmatroska-dev_1.3.0-2_i386 +libmbt0-dev_3.2.8-1_i386 +libmcpp-dev_2.7.2-1.1_i386 +libmcrypt-dev_2.5.8-3.1_i386 +libmcs-dev_0.7.2-2.1_i386 +libmd3-dev_0.1.92-4_i386 +libmdc2-dev_0.10.7-1+b2_i386 +libmdds-dev_0.5.4-1_all +libmdsp-dev_0.11-10_i386 +libmeanwhile-dev_1.0.2-4_i386 +libmecab-dev_0.99.3-3_i386 +libmed-dev_3.0.3-3_i386 +libmedc-dev_3.0.3-3_i386 +libmediainfo-dev_0.7.58-1_i386 +libmediastreamer-dev_3.5.2-10_i386 +libmedimport-dev_3.0.3-3_i386 +libmeep-dev_1.1.1-8+deb7u1_i386 +libmeep-lam4-dev_1.1.1-10~deb7u1_i386 +libmeep-mpi-default-dev_1.1.1-10~deb7u1_i386 +libmeep-mpich2-dev_1.1.1-10~deb7u1_i386 +libmeep-openmpi-dev_1.1.1-9~deb7u2_i386 +libmelt-ocaml-dev_1.4.0-1_i386 +libmemcache-dev_1.4.0.rc2-1_i386 +libmemcached-dev_1.0.8-1_i386 +libmemphis-0.2-dev_0.2.3-2_i386 +libmenhir-ocaml-dev_20120123.dfsg-1_i386 +libmenu-cache1-dev_0.3.3-1_i386 +libmercator-0.3-dev_0.3.0-2_i386 +libmeschach-dev_1.2b-13_i386 +libmetacity-dev_1:2.34.3-4_i386 +libmgl-dev_1.11.2-17_i386 +libmhash-dev_0.9.9.9-1.1_i386 +libmicrohttpd-dev_0.9.20-1+deb7u1_i386 +libmigemo-dev_20110227-7_i386 +libmikmatch-ocaml-dev_1.0.4-1+b1_i386 +libmikmod2-dev_3.1.12-5_i386 +libmilter-dev_8.14.4-4_i386 +libmimedir-dev_0.5.1-4_i386 +libmimedir-gnome-dev_0.4.2-5_i386 +libmimelib1-dev_5:1.1.4-2_i386 +libmimetic-dev_0.9.7-3_i386 +libmimic-dev_1.0.4-2.1_i386 +libminc-dev_2.1.10-1+b1_i386 +libming-dev_1:0.4.4-1.1_i386 +libmini18n-dev_0.2.1-1_i386 +libminidjvu-dev_0.8.svn.2010.05.06+dfsg-0.2_i386 +libminiupnpc-dev_1.5-2_i386 +libmission-control-plugins-dev_1:5.12.3-1_i386 +libmkv-dev_0.6.5.1-1_i386 +libmlpcap-ocaml-dev_0.9-16_i386 +libmlpost-ocaml-dev_0.8.1-3_i386 +libmlt++-dev_0.8.0-4_i386 +libmlt-dev_0.8.0-4_i386 +libmlx4-dev_1.0.4-1_i386 +libmm-dev_1.4.2-4_i386 +libmm-ocaml-dev_0.2.0-1+b1_i386 +libmmpong0.9-dev_0.9.1-2.1_i386 +libmms-dev_0.6.2-3_i386 +libmng-dev_1.0.10-3_i386 +libmnl-dev_1.0.3-3_i386 +libmodbus-dev_3.0.3-1_i386 +libmodglue1-dev_1.17-2.1_all +libmodplug-dev_1:0.8.8.4-3+deb7u1+git20130828_all +libmoe-dev_1.5.8-1_i386 +libmongo-client-dev_0.1.5-1+deb7u1_i386 +libmono-2.0-dev_2.10.8.1-8_i386 +libmono-addins-cil-dev_0.6.2-2_all +libmono-addins-gui-cil-dev_0.6.2-2_all +libmono-addins-msbuild-cil-dev_0.6.2-2_all +libmono-cecil-cil-dev_0.9.5+dfsg-2_all +libmono-cecil-flowanalysis-cil-dev_0.1~vcs20110809.r1.b34edf6-2_all +libmono-cil-dev_2.10.8.1-8_all +libmono-reflection-cil-dev_1.0+git20110407+d2343843-2_all +libmono-uia-cil-dev_2.1-4_all +libmono-upnp-cil-dev_0.1.2-1_all +libmono-zeroconf-cil-dev_0.9.0-4_all +libmonogame-cil-dev_2.5.1+dfsg-3_all +libmopac7-dev_1.15-5_i386 +libmorph-dev_1:20090926_i386 +libmosquitto0-dev_0.15-2_all +libmosquittopp0-dev_0.15-2_all +libmount-dev_2.20.1-5.3_i386 +libmowgli-dev_1.0.0-1_i386 +libmozjs-dev_24.4.0esr-1~deb7u2_i386 +libmozjs185-dev_1.8.5-1.0.0+dfsg-4_i386 +libmp3lame-dev_3.99.5+repack1-3_i386 +libmp3lame-ocaml-dev_0.3.1-1+b1_i386 +libmp3splt-dev_0.7.2-2_i386 +libmp4v2-dev_2.0.0~dfsg0-1_i386 +libmpc-dev_0.9-4_i386 +libmpcdec-dev_2:0.1~r459-4_i386 +libmpd-dev_0.20.0-1.1_i386 +libmpdclient-dev_2.3-1_i386 +libmpeg2-4-dev_0.4.1-3_i386 +libmpeg3-dev_1.5.4-5_i386 +libmpfi-dev_1.5.1-1_i386 +libmpfr-dev_3.1.0-5_i386 +libmpg123-dev_1.14.4-1_i386 +libmpich2-dev_1.4.1-4.2_i386 +libmpikmeans-dev_1.5-1+b1_i386 +libmrml1-dev_0.1.14-12_i386 +libmrmpi-dev_1.0~20110620.dfsg-2_i386 +libmrss0-dev_0.19.2-3_i386 +libmsgpack-dev_0.5.7-2_i386 +libmsn-dev_4.2-2_i386 +libmsv-dev_0.0.0-1_i386 +libmtbl-dev_0.2-1_i386 +libmtcp-dev_1.2.5-1_i386 +libmtdev-dev_1.1.2-1_i386 +libmthca-dev_1.0.6-1_i386 +libmtp-dev_1.1.3-35-g0ece104-5_i386 +libmudflap0-4.4-dev_4.4.7-2_i386 +libmudflap0-4.6-dev_4.6.3-14_i386 +libmudflap0-4.7-dev_4.7.2-5_i386 +libmulticobex1-dev_0.23-1.1_i386 +libmumps-dev_4.10.0.dfsg-3_i386 +libmumps-ptscotch-dev_4.10.0.dfsg-3_i386 +libmumps-scotch-dev_4.10.0.dfsg-3_i386 +libmumps-seq-dev_4.10.0.dfsg-3_i386 +libmunge-dev_0.5.10-1_i386 +libmuparser-dev_2.1.0-3_i386 +libmupdf-dev_0.9-2_i386 +libmupen64plus-dev_1.99.5-6_all +libmuroar-dev_0.1.8-2_i386 +libmusic-dev_1.0.7-1.2_i386 +libmusicbrainz3-dev_3.0.2-2.1_i386 +libmusicbrainz5-dev_5.0.1-2_i386 +libmutter-dev_3.4.1-5_i386 +libmx-dev_1.4.6-1_i386 +libmxml-dev_2.6-2_i386 +libmyproxy-dev_5.6-1_i386 +libmysql++-dev_3.1.0-2+b1_i386 +libmysql-cil-dev_6.4.3-2_all +libmysql-ocaml-dev_1.1.1-1_i386 +libmysqlclient-dev_5.5.35+dfsg-0+wheezy1_i386 +libmysqlcppconn-dev_1.1.0-4+b1_i386 +libmysqld-dev_5.5.35+dfsg-0+wheezy1_i386 +libmythes-dev_2:1.2.2-1_i386 +libnabrit-dev_0.4.1-1_i386 +libnacl-dev_20110221-4_i386 +libnacore-dev_0.4.0-3_i386 +libnanohttp-dev_1.1.0-17.1_i386 +libnatpmp-dev_20110808-3_i386 +libnautilus-extension-dev_3.4.2-1+build1_i386 +libnbio-dev_0.30-1_i386 +libncap-dev_1.9.2-1+b2_i386 +libncbi6-dev_6.1.20120620-2_i386 +libncp-dev_2.2.6-9_i386 +libncurses5-dev_5.9-10_i386 +libncursesada2-dev_5.9.20110404-7_i386 +libncursesw5-dev_5.9-10_i386 +libndesk-dbus-glib1.0-cil-dev_0.4.1-4_all +libndesk-dbus1.0-cil-dev_0.6.0-6_all +libndr-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libndr-standard-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libnecpp-dev_1.5.0+cvs20101003-2.1_i386 +libneon27-dev_0.29.6-3_i386 +libneon27-gnutls-dev_0.29.6-3_i386 +libnes-dev_1.1.3-1_i386 +libnet1-dev_1.1.4-2.1_i386 +libnet6-1.3-dev_1:1.3.14-1_i386 +libnetcdf-dev_1:4.1.3-6+b1_i386 +libnetcf-dev_0.1.9-2_i386 +libnetclasses-dev_1.06.dfsg-5+b3_i386 +libnetfilter-conntrack-dev_1.0.1-1_i386 +libnetfilter-cttimeout-dev_1.0.0-1_i386 +libnetfilter-log-dev_1.0.0-1_i386 +libnetfilter-queue-dev_0.0.17-1_i386 +libnethttpd-ocaml-dev_3.5.1-1_i386 +libnetpbm10-dev_2:10.0-15+b1_i386 +libnetpbm9-dev_2:10.0-15+b1_i386 +libnetsvcs-dev_6.0.3+dfsg-0.1_i386 +libnewlib-dev_1.18.0-6.2_i386 +libnewmat10-dev_1.10.4-5_i386 +libnewt-dev_0.52.14-11.1_i386 +libnewtonsoft-json-cil-dev_4.5r6-1_all +libnexus0-dev_4.2.1-svn1614-1+b2_i386 +libnfnetlink-dev_1.0.0-1.1_i386 +libnfo-dev_1.0.1-1_i386 +libnfs-dev_1.3.0-2_i386 +libnfsidmap-dev_0.25-4_i386 +libnice-dev_0.1.2-1_i386 +libnids-dev_1.23-2_i386 +libnifti-dev_2.0.0-1_i386 +libnih-dbus-dev_1.0.3-4.1_i386 +libnih-dev_1.0.3-4.1_i386 +libnini-cil-dev_1.1.0+dfsg.2-4_all +libnjb-dev_2.2.7~dfsg0-3_i386 +libnl-3-dev_3.2.7-4_i386 +libnl-cli-3-dev_3.2.7-4_i386 +libnl-dev_1.1-7_i386 +libnl-genl-3-dev_3.2.7-4_i386 +libnl-nf-3-dev_3.2.7-4_i386 +libnl-route-3-dev_3.2.7-4_i386 +libnm-glib-dev_0.9.4.0-10_i386 +libnm-glib-vpn-dev_0.9.4.0-10_i386 +libnm-gtk-dev_0.9.4.1-5_i386 +libnm-util-dev_0.9.4.0-10_i386 +libnmz7-dev_2.0.21-6_i386 +libnoise-dev_1.0.0+nmu1_i386 +libnotify-cil-dev_0.4.0~r3032-6_all +libnotify-dev_0.7.5-1_i386 +libnotmuch-dev_0.13.2-1_i386 +libnova-dev_0.14.0-2_i386 +libnpth0-dev_0.90-2_i386 +libnsbmp0-dev_0.0.1-1.1_i386 +libnsgif0-dev_0.0.1-1.1_i386 +libnspr4-dev_2:4.9.2-1+deb7u1_i386 +libnss3-dev_2:3.14.5-1_i386 +libntfs-dev_2.0.0-1+b1_i386 +libntl-dev_5.5.2-2_i386 +libntlm0-dev_1.2-1_i386 +libntrack-dev_016-1.1_i386 +libntrack-glib-dev_016-1.1_i386 +libntrack-gobject-dev_016-1.1_i386 +libntrack-qt4-dev_016-1.1_i386 +libnuclient-dev_2.4.3-2.2_i386 +libnuma-dev_2.0.8~rc4-1_i386 +libnunit-cil-dev_2.6.0.12051+dfsg-2_all +libnussl-dev_2.4.3-2.2_i386 +libnvtt-dev_2.0.8-1+dfsg-2_i386 +libnxcl-dev_0.9-3.1_i386 +libnxml0-dev_0.18.3-4_i386 +libnzb-dev_0.0.20050629-6.1_i386 +liboasis-ocaml-dev_0.2.0-6_i386 +liboasis3-dev_3.3.beta.dfsg.1-8+b1_i386 +liboath-dev_1.12.4-1_i386 +liboauth-dev_0.9.4-3.1_i386 +libobby-0.4-dev_0.4.8-1_i386 +libobexftp0-dev_0.23-1.1_i386 +libobrowser-ocaml-dev_1.1.1+dfsg-1+b9_i386 +libobus-ocaml-dev_1.1.3-1+b8_i386 +libocamlbricks-ocaml-dev_0.50.1-4+b5_i386 +libocamlgraph-ocaml-dev_1.8.2-2_i386 +libocamlgraph-viewer-ocaml-dev_1.8.2-2_i386 +libocamlgsl-ocaml-dev_0.6.0-7+b2_i386 +libocamlnet-gtk2-ocaml-dev_3.5.1-1_i386 +libocamlnet-ocaml-dev_3.5.1-1_i386 +libocamlnet-ssl-ocaml-dev_3.5.1-1_i386 +libocamlodbc-ocaml-dev_2.15-5+b3_i386 +libocamlviz-ocaml-dev_1.01-2+b2_i386 +libocas-dev_0.93-1_i386 +liboce-foundation-dev_0.9.1-3_i386 +liboce-modeling-dev_0.9.1-3_all +liboce-ocaf-dev_0.9.1-3_all +liboce-ocaf-lite-dev_0.9.1-3_all +liboce-visualization-dev_0.9.1-3_all +libocpf-dev_1:1.0-3_i386 +libocrad-dev_0.22~rc1-2_i386 +libocsigen-ocaml-dev_1.3.4-2+b12_i386 +libocsigen-xhtml-ocaml-dev_1.3.4-2+b12_i386 +libocsigenserver-ocaml-dev_2.1-1_i386 +liboctave-dev_3.6.2-5+deb7u1_i386 +libode-dev_2:0.11.1-4_i386 +libode-sp-dev_2:0.11.1-4_i386 +libodin-dev_1.8.5-2_i386 +libodn-ocaml-dev_0.0.8-1_i386 +libofa0-dev_0.9.3-5_i386 +libofapi-dev_0git20070620-6_i386 +libofdt-dev_1.3.6-1_i386 +libofetion-dev_2.2.2-1_i386 +libofx-dev_1:0.9.4-2.1_i386 +libogdi3.2-dev_3.2.0~beta2-7_i386 +libogg-dev_1.3.0-4_i386 +libogg-ocaml-dev_0.4.3-1+b1_i386 +liboggkate-dev_0.4.1-1_i386 +liboggplay1-dev_0.2.1~git20091227-1.2_i386 +liboggz2-dev_1.1.1-1_i386 +liboglappth-dev_1.0.0-2_i386 +libogre-1.8-dev_1.8.0+dfsg1-3_i386 +libogre-dev_1.7.4+dfsg1-7_i386 +liboil0.3-dev_0.3.17-2_i386 +libois-dev_1.3.0+dfsg0-5_i386 +libomhacks-dev_0.16-1_i386 +libomnievents-dev_1:2.6.2-2_i386 +libomniorb4-dev_4.1.6-2_i386 +libomnithread3-dev_4.1.6-2_i386 +libomxil-bellagio-dev_0.9.3-1+b1_i386 +libonig-dev_5.9.1-1_i386 +liboobs-1-dev_3.0.0-1_i386 +liboop-dev_1.0-9_i386 +libooptools-dev_2.7-1_i386 +libopal-dev_3.10.4~dfsg-3_i386 +libopenafs-dev_1.6.1-3+deb7u2_i386 +libopenais-dev_1.1.4-4.1_i386 +libopenal-dev_1:1.14-4_i386 +libopenbabel-dev_2.3.1+dfsg-4_i386 +libopenblas-dev_0.1.1-6+deb7u3_i386 +libopencc-dev_0.3.0-3_i386 +libopenconnect-dev_3.20-4_i386 +libopencore-amrnb-dev_0.1.3-2_i386 +libopencore-amrwb-dev_0.1.3-2_i386 +libopencryptoki-dev_2.3.1+dfsg-3_i386 +libopencsg-dev_1.3.2-2_i386 +libopenct1-dev_0.6.20-1.2_i386 +libopencv-calib3d-dev_2.3.1-11_i386 +libopencv-contrib-dev_2.3.1-11_i386 +libopencv-core-dev_2.3.1-11_i386 +libopencv-dev_2.3.1-11_i386 +libopencv-features2d-dev_2.3.1-11_i386 +libopencv-flann-dev_2.3.1-11_i386 +libopencv-gpu-dev_2.3.1-11_i386 +libopencv-highgui-dev_2.3.1-11_i386 +libopencv-imgproc-dev_2.3.1-11_i386 +libopencv-legacy-dev_2.3.1-11_i386 +libopencv-ml-dev_2.3.1-11_i386 +libopencv-objdetect-dev_2.3.1-11_i386 +libopencv-video-dev_2.3.1-11_i386 +libopendkim-dev_2.6.8-4_i386 +libopenexr-dev_1.6.1-6_i386 +libopenhpi-dev_2.14.1-1.2_i386 +libopenigtlink1-dev_1.9.2~svn7468-1_i386 +libopenimageio-dev_1.0.5+dfsg0-1_i386 +libopenipmi-dev_2.0.16-1.3_i386 +libopenjpeg-dev_1.3+dfsg-4.7_i386 +libopenmeeg-dev_2.0.0.dfsg-5_i386 +libopenmpi-dev_1.4.5-1_i386 +libopenobex1-dev_1.5-2_i386 +libopenr2-dev_1.3.2-1.1_i386 +libopenraw-dev_0.0.9-3+b1_i386 +libopenrawgnome-dev_0.0.9-3+b1_i386 +libopenscap-dev_0.8.0-4+b1_i386 +libopenscenegraph-dev_3.0.1-4_i386 +libopenslide-dev_3.2.6-2_i386 +libopensm2-dev_3.2.6-20090317-2.1_i386 +libopenthreads-dev_3.0.1-4_i386 +libopentk-cil-dev_1.0.20101006+dfsg1-1_all +libopentoken3-dev_4.0b-3_i386 +libopenturns-dev_1.0-4_i386 +libopenusb-dev_1.1.0-2_i386 +libopenvg1-mesa-dev_8.0.5-4+deb7u2_i386 +libopenvrml-dev_0.18.9-5+deb7u1_i386 +libopenwalnut1-dev_1.2.5-1.1+b1_i386 +liboping-dev_1.6.2-1_i386 +libopkele-dev_2.0.4-5.3_i386 +libopts25-dev_1:5.12-0.1_i386 +libopus-dev_0.9.14+20120615-1+nmu1_i386 +liborange-dev_0.4-2_i386 +liborbit2-dev_1:2.14.19-0.1_i386 +liborc-0.4-dev_1:0.4.16-2_i386 +liborigin-dev_20080225-2.1_i386 +liborigin2-dev_2:20110117-1+b2_i386 +libortp-dev_3.5.2-10_i386 +liboscpack-dev_1.0.2-1_i386 +libosgearth-dev_2.0+dfsg-4+b3_i386 +libosinfo-1.0-dev_0.1.1-1_i386 +libosip2-dev_3.6.0-4_i386 +libosl-dev_0.5.0-1_i386 +libosmesa6-dev_8.0.5-4+deb7u2_i386 +libosmgpsmap-dev_0.7.3-3_i386 +libosmium-dev_0.0~20111213-g7f3500a-3+b2_i386 +libosmpbf-dev_1.2.1-3_i386 +libosp-dev_1.5.2-10_i386 +libosptk3-dev_3.4.2-1+b1_i386 +libossim-dev_1.7.21-4_i386 +libossp-sa-dev_1.2.6-1_i386 +libossp-uuid-dev_1.6.2-1.3_i386 +libostyle-dev_1.4devel1-20.1+b1_i386 +libotcl1-dev_1.14+dfsg-2_i386 +libotf-dev_0.9.12-2_i386 +libotf-trace-dev_1.10.2+dfsg-2_i386 +libotpw-dev_1.3-2_i386 +libotr2-dev_3.2.1-1+deb7u1_i386 +libots-dev_0.5.0-2.1_i386 +libounit-ocaml-dev_1.1.1-1_i386 +libow-dev_2.8p15-1_i386 +libowfat-dev_0.28-6_i386 +libowfat-dietlibc-dev_0.28-6_i386 +libownet-dev_2.8p15-1_i386 +libp11-dev_0.2.8-2_i386 +libp11-kit-dev_0.12-3_i386 +libpackagekit-glib2-dev_0.7.6-3_i386 +libpackagekit-qt2-dev_0.7.6-3_i386 +libpacketdump3-dev_3.0.14-1_i386 +libpacklib-lesstif1-dev_20061220+dfsg3-2_i386 +libpacklib1-dev_20061220+dfsg3-2_i386 +libpacparser-dev_1.3.0-2_i386 +libpam-ocaml-dev_1.1-4+b3_i386 +libpam0g-dev_1.1.3-7.1_i386 +libpanel-applet-4-dev_3.4.2.1-4_i386 +libpango1.0-dev_1.30.0-1_i386 +libpangomm-1.4-dev_2.28.4-1_i386 +libpano13-dev_2.9.18+dfsg-5_i386 +libpantomime1.2-dev_1.2.0~pre3+snap20071004+dfsg-4+b1_i386 +libpaper-dev_1.1.24+nmu2_i386 +libpaps-dev_0.6.8-6_i386 +libpaq-dev_1.0.4-3+b1_i386 +libpar2-0-dev_0.2.1-1_i386 +libpari-dev_2.5.1-2_i386 +libparpack2-dev_3.1.1-2.1_i386 +libparrot-dev_4.0.0-3_i386 +libparser++-dev_0.2.3-2_all +libparted0-dev_2.3-12_i386 +libpasswdqc-dev_1.2.0-1_all +libpath-utils-dev_0.1.3-2_i386 +libpathfinder-dev_1.1.3-0.4+b1_i386 +libpawlib-lesstif3-dev_1:2.14.04.dfsg.2-8_i386 +libpawlib2-dev_1:2.14.04.dfsg.2-8_i386 +libpcap-dev_1.3.0-1_all +libpcap0.8-dev_1.3.0-1_i386 +libpcapnav0-dev_0.8-1_i386 +libpci-dev_1:3.1.9-6_i386 +libpciaccess-dev_0.13.1-2_i386 +libpcl1-dev_1.6-1_i386 +libpcre++-dev_0.9.5-5.1_i386 +libpcre-ocaml-dev_6.2.5-1_i386 +libpcre3-dev_1:8.30-5_i386 +libpcscada2-dev_0.7.1-4_i386 +libpcsclite-dev_1.8.4-1+deb7u1_i386 +libpdflib804-2-dev_20061220+dfsg3-2_i386 +libpe-rules2-dev_1.1.7-1_i386 +libpe-status3-dev_1.1.7-1_i386 +libpeas-dev_1.4.0-2_i386 +libpengine3-dev_1.1.7-1_i386 +libperl-dev_5.14.2-21+deb7u1_i386 +libperl4caml-ocaml-dev_0.9.5-4+b4_i386 +libpetsc3.2-dev_3.2.dfsg-6_i386 +libpfqueue-dev_0.5.6-8_i386 +libpfs-dev_1.8.5-1_i386 +libpgm-dev_5.1.118-1~dfsg-0.1_i386 +libpgocaml-ocaml-dev_1.5-2_i386 +libpgpool-dev_3.1.3-5_i386 +libpgtcl-dev_1:1.5-6_i386 +libphash0-dev_0.9.4-1.2_i386 +libphat-dev_0.4.1-5_i386 +libphobos-4.4-dev_1.063-4.4.7-1_i386 +libphobos2-4.6-dev_0.29.1-4.6.3-2_i386 +libphone-ui-dev_1:0.0.1+git20110825-3_i386 +libphone-utils-dev_0.1+git20110523-2.1_i386 +libphonon-dev_4:4.6.0.0-3_i386 +libphononexperimental-dev_4:4.6.0.0-3_i386 +libphotos202-dev_20061220+dfsg3-2_i386 +libphtools2-dev_20061220+dfsg3-2_i386 +libphysfs-dev_2.0.2-6_i386 +libpiano-dev_2012.05.06-2_i386 +libpigment0.3-dev_0.3.17-1_i386 +libpils2-dev_1.0.9+hg2665-1_i386 +libpinyin0-dev_0.6.91-1_i386 +libpion-common-dev_4.0.7+dfsg-3.1_i386 +libpion-net-dev_4.0.7+dfsg-3.1_i386 +libpipeline-dev_1.2.1-1_i386 +libpisock-dev_0.12.5-5_i386 +libpixman-1-dev_0.26.0-4+deb7u1_i386 +libpkcs11-helper1-dev_1.09-1_i386 +libplayer-dev_2.0.1-2.1_i386 +libplayerc++3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerc3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercommon3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercore3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerdrivers3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerinterface3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerjpeg3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayertcp3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerwkb3.0-dev_3.0.2+dfsg-4+b1_i386 +libplib-dev_1.8.5-6_i386 +libplist++-dev_1.8-1_i386 +libplist-dev_1.8-1_i386 +libpload-dev_1.4.2-3_i386 +libplot-dev_2.6-3_i386 +libploticus0-dev_2.41-5_i386 +libplotmm-dev_0.1.2-2_i386 +libplplot-ada0-dev_5.9.9-5_i386 +libplplot-dev_5.9.9-5_i386 +libplumb2-dev_1.0.9+hg2665-1_i386 +libplumbgpl2-dev_1.0.9+hg2665-1_i386 +libpmap3.0-dev_3.0.2+dfsg-4+b1_i386 +libpmi0-dev_2.3.4-2+b1_i386 +libpmount-dev_0.0.16_i386 +libpng++-dev_0.2.5-1_all +libpng12-dev_1.2.49-1_i386 +libpnglite-dev_0.1.17-1_i386 +libpoco-dev_1.3.6p1-4_i386 +libpodofo-dev_0.9.0-1.1+b1_i386 +libpoker-eval-dev_138.0-1_i386 +libpolarssl-dev_1.2.9-1~deb7u2_i386 +libpoldiff-dev_3.3.7-3_i386 +libpolkit-agent-1-dev_0.105-3_i386 +libpolkit-backend-1-dev_0.105-3_i386 +libpolkit-gobject-1-dev_0.105-3_i386 +libpolkit-qt-1-dev_0.103.0-1_i386 +libpolybori-dev_0.5~rc1-2.2_i386 +libpolylib64-dev_5.22.5-3+dfsg_i386 +libpolyml-dev_5.2.1-1.1_i386 +libpolyorb2-dev_2.8~20110207-5.1_i386 +libpomp-dev_1.1+dfsg-2_i386 +libpoppler-cil-dev_0.0.3-2_all +libpoppler-cpp-dev_0.18.4-6_i386 +libpoppler-dev_0.18.4-6_i386 +libpoppler-glib-dev_0.18.4-6_i386 +libpoppler-private-dev_0.18.4-6_i386 +libpoppler-qt4-dev_0.18.4-6_i386 +libpopplerkit-dev_0.0.20051227svn-7+b1_i386 +libpopt-dev_1.16-7_i386 +libportaudio-dev_18.1-7.1_i386 +libportaudio-ocaml-dev_0.2.0-1+b1_i386 +libportmidi-dev_1:184-2.1_i386 +libportsmf-dev_0.1~svn20101010-3_i386 +libpostgresql-ocaml-dev_1.18.0-1_i386 +libpostproc-dev_6:0.8.10-1_i386 +libpotrace-dev_1.10-1_i386 +libpowerman0-dev_2.3.5-1_i386 +libppd-dev_2:0.10-7.1_i386 +libppl0.11-dev_0.11.2-8_i386 +libpq-dev_9.1.13-0wheezy1_i386 +libpqxx3-dev_3.1-1.1_i386 +libprelude-dev_1.0.0-9_i386 +libpreludedb-dev_1.0.0-1.1+b2_i386 +libpresage-dev_0.8.8-1_i386 +libpri-dev_1.4.12-2_i386 +libprinterconf-dev_0.5-12_i386 +libprintsys-dev_0.6-13_i386 +libprison-dev_1.0+dfsg-1_i386 +libprocps0-dev_1:3.3.3-3_i386 +libproj-dev_4.7.0-2_i386 +libprojectm-dev_2.1.0+dfsg-1_i386 +libprojectm-qt-dev_2.1.0+dfsg-1_i386 +libprotobuf-c0-dev_0.14-1+b1_i386 +libprotobuf-dev_2.4.1-3_i386 +libprotoc-dev_2.4.1-3_i386 +libproxy-dev_0.3.1-6_i386 +libproxychains-dev_3.1-3_i386 +libpspell-dev_0.60.7~20110707-1_i386 +libpst-dev_0.6.54-4.1_i386 +libpstoedit-dev_3.60-2+b1_i386 +libpstreams-dev_0.7.0-2_all +libpt-dev_2.10.4~dfsg-1_i386 +libptexenc-dev_2012.20120628-4_i386 +libpth-dev_2.0.7-16_i386 +libpthread-stubs0-dev_0.3-3_i386 +libpthread-workqueue-dev_0.8.2-1_i386 +libptscotch-dev_5.1.12b.dfsg-1.2_i386 +libpugl-dev_0~svn32+dfsg0-1_i386 +libpulse-dev_2.0-6.1_i386 +libpulse-ocaml-dev_0.1.2-1+b1_i386 +libpuma-dev_1:1.1+svn20120529-2_i386 +libpurelibc-dev_0.4.1-1_i386 +libpurple-dev_2.10.9-1~deb7u1_all +libpuzzle-dev_0.9-5_i386 +libpwl-dev_0.11.2-8_i386 +libpxp-ocaml-dev_1.2.2-1+b4_i386 +libpycaml-ocaml-dev_0.82-14+b2_i386 +libpyside-dev_1.1.1-3_i386 +libpythia8-dev_8.1.65-1_i386 +libpythonqt2-dev_2.0.1-1.1_i386 +libqalculate-dev_0.9.7-8_i386 +libqapt-dev_1.3.0-2_i386 +libqb-dev_0.11.1-2_i386 +libqca2-dev_2.0.3-4_i386 +libqd-dev_2.3.11.dfsg-2.1_i386 +libqdaccolib-dev_0.8.2-1_i386 +libqdbm++-dev_1.8.78-2_i386 +libqdbm-dev_1.8.78-2_i386 +libqdjango-dev_0.2.5-2_i386 +libqedje-dev_0.4.0+lgpl-3_i386 +libqfits-dev_6.2.0-5_i386 +libqgis-dev_1.7.4+1.7.5~20120320-1.1+b1_i386 +libqglviewer-qt4-dev_2.3.4-4.2_i386 +libqgpsmm-dev_3.6-4+deb7u1_i386 +libqhull-dev_2009.1-3_i386 +libqimageblitz-dev_1:0.0.6-4_i386 +libqjson-dev_0.7.1-7_i386 +libqmf-dev_0.16-6+deb7u1_i386 +libqmf2-dev_0.16-6+deb7u1_i386 +libqmfconsole2-dev_0.16-6+deb7u1_i386 +libqmfengine1-dev_0.16-6+deb7u1_i386 +libqmmp-dev_0.5.5-1+b1_i386 +libqmmpui-dev_0.5.5-1+b1_i386 +libqoauth-dev_1.0.1-1_i386 +libqof-dev_0.8.6-1_i386 +libqofexpensesobjects-dev_0.1.9-2_i386 +libqpdf-dev_2.3.1-4_i386 +libqpidbroker2-dev_0.16-6+deb7u1_i386 +libqpidclient2-dev_0.16-6+deb7u1_i386 +libqpidcommon2-dev_0.16-6+deb7u1_i386 +libqpidmessaging2-dev_0.16-6+deb7u1_i386 +libqpidtypes1-dev_0.16-6+deb7u1_i386 +libqpol-dev_3.3.7-3_i386 +libqpx-dev_0.7.1.002-5_i386 +libqrencode-dev_3.3.0-2_i386 +libqrupdate-dev_1.1.1-1_i386 +libqsastime-dev_5.9.9-5_i386 +libqscintilla2-dev_2.6.2-2_all +libqt4-dev_4:4.8.2+dfsg-11_i386 +libqt4-opengl-dev_4:4.8.2+dfsg-11_i386 +libqt4-private-dev_4:4.8.2+dfsg-11_i386 +libqt4pas-dev_2.5-6_i386 +libqtassistantclient-dev_4.6.3-4_i386 +libqtexengine-dev_0.3-3_i386 +libqtgstreamer-dev_0.10.2-2_i386 +libqtruby4shared-dev_4:4.8.4-1_i386 +libqtwebkit-dev_2.2.1-5_i386 +libquantlib0-dev_1.2-2+b1_i386 +libquantum-dev_1.1.0-3_i386 +libquicktime-dev_2:1.2.4-3_i386 +libquorum-dev_1.4.2-3_i386 +libquvi-dev_0.4.1-1_i386 +libqwt-dev_6.0.0-1.2_i386 +libqwt5-qt4-dev_5.2.2-3_i386 +libqwtplot3d-qt4-dev_0.2.7+svn191-7_i386 +libqxmlrpc-dev_0.0.svn6-2_i386 +libqxmpp-dev_0.4.92-1_i386 +libqxt-dev_0.6.1-6_i386 +libqzeitgeist-dev_0.7.0-1+b1_i386 +libqzion-dev_0.4.0+lgpl-4_i386 +librabbitmq-dev_0.0.1.hg216-1_i386 +libradare2-dev_0.9-3_i386 +libradius1-dev_0.3.2-14_i386 +libradiusclient-ng-dev_0.5.6-1.1_i386 +libranlip-dev_1.0-4.1_i386 +librapi2-dev_0.15-2.1_i386 +libraptor1-dev_1.4.21-7.1_i386 +libraptor2-dev_2.0.8-2_i386 +librarian-dev_0.8.1-5_i386 +librasqal3-dev_0.9.29-1_i386 +librasterlite-dev_1.1~svn11-2_i386 +libraul-dev_0.8.0+dfsg0-0.1+b1_i386 +libraw-dev_0.14.6-2_i386 +libraw1394-dev_2.0.9-1_i386 +librcc-dev_0.2.9-3_i386 +librcd-dev_0.1.13-3_i386 +librdf0-dev_1.0.15-1+b1_i386 +librdkit-dev_201203-3_i386 +librdmacm-dev_1.0.15-1+deb7u1_i386 +librdmawrap2-dev_0.16-6+deb7u1_i386 +libreact-ocaml-dev_0.9.3-1_i386 +libreadline-dev_6.2+dfsg-0.1_i386 +libreadline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +libreadline6-dev_6.2+dfsg-0.1_i386 +librec-dev_1.5-1_i386 +librecode-dev_3.6-20_i386 +libref-array-dev_0.1.3-2_i386 +libregina3-dev_3.6-2_i386 +libregistry-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libreins-ocaml-dev_0.1a-4+b1_i386 +libreiser4-dev_1.0.7-6.3_i386 +librelp-dev_1.0.0-1_i386 +libremctl-dev_3.2-4_i386 +librenaissance0-dev_0.9.0-4+b3_i386 +libreoffice-dev_1:3.5.4+dfsg2-0+deb7u2_i386 +librep-dev_0.90.2-1.3_i386 +libreplaygain-dev_1.0~r475-1_i386 +libres-ocaml-dev_3.2.0-2+b3_i386 +libresample1-dev_0.1.3-4_i386 +libresid-builder-dev_2.1.1-14_i386 +libresiprocate-1.8-dev_1.8.5-4_i386 +libresiprocate-turn-client-1.8-dev_1.8.5-4_i386 +librest-dev_0.7.12-3_i386 +librest-extras-dev_0.7.12-3_i386 +librhash-cil-dev_1.2.9-8+deb7u1_all +librhash-dev_1.2.9-8+deb7u1_i386 +librheolef-dev_6.1-2.1_i386 +librivet-dev_1.8.0-1_i386 +librlog-dev_1.4-2_i386 +libroar-dev_1.0~beta2-3_i386 +libroot-bindings-python-dev_5.34.00-2_i386 +libroot-bindings-ruby-dev_5.34.00-2_i386 +libroot-core-dev_5.34.00-2_i386 +libroot-geom-dev_5.34.00-2_i386 +libroot-graf2d-gpad-dev_5.34.00-2_i386 +libroot-graf2d-graf-dev_5.34.00-2_i386 +libroot-graf2d-postscript-dev_5.34.00-2_i386 +libroot-graf3d-eve-dev_5.34.00-2_i386 +libroot-graf3d-g3d-dev_5.34.00-2_i386 +libroot-graf3d-gl-dev_5.34.00-2_i386 +libroot-gui-dev_5.34.00-2_i386 +libroot-gui-ged-dev_5.34.00-2_i386 +libroot-hist-dev_5.34.00-2_i386 +libroot-hist-spectrum-dev_5.34.00-2_i386 +libroot-html-dev_5.34.00-2_i386 +libroot-io-dev_5.34.00-2_i386 +libroot-io-xmlparser-dev_5.34.00-2_i386 +libroot-math-foam-dev_5.34.00-2_i386 +libroot-math-genvector-dev_5.34.00-2_i386 +libroot-math-mathcore-dev_5.34.00-2_i386 +libroot-math-mathmore-dev_5.34.00-2_i386 +libroot-math-matrix-dev_5.34.00-2_i386 +libroot-math-minuit-dev_5.34.00-2_i386 +libroot-math-mlp-dev_5.34.00-2_i386 +libroot-math-physics-dev_5.34.00-2_i386 +libroot-math-quadp-dev_5.34.00-2_i386 +libroot-math-smatrix-dev_5.34.00-2_i386 +libroot-math-splot-dev_5.34.00-2_i386 +libroot-math-unuran-dev_5.34.00-2_i386 +libroot-misc-memstat-dev_5.34.00-2_i386 +libroot-misc-minicern-dev_5.34.00-2_i386 +libroot-misc-table-dev_5.34.00-2_i386 +libroot-montecarlo-eg-dev_5.34.00-2_i386 +libroot-montecarlo-vmc-dev_5.34.00-2_i386 +libroot-net-auth-dev_5.34.00-2_i386 +libroot-net-bonjour-dev_5.34.00-2_i386 +libroot-net-dev_5.34.00-2_i386 +libroot-net-ldap-dev_5.34.00-2_i386 +libroot-proof-clarens-dev_5.34.00-2_i386 +libroot-proof-dev_5.34.00-2_i386 +libroot-proof-proofplayer-dev_5.34.00-2_i386 +libroot-roofit-dev_5.34.00-2_i386 +libroot-tmva-dev_5.34.00-2_i386 +libroot-tree-dev_5.34.00-2_i386 +libroot-tree-treeplayer-dev_5.34.00-2_i386 +librostlab-blast0-dev_1.0.0-2_i386 +librostlab3-dev_1.0.20-1_i386 +librpcsecgss-dev_0.19-5_i386 +librplay3-dev_3.3.2-14_i386 +librpm-dev_4.10.0-5+deb7u1_i386 +librra-dev_0.14-1.2_i386 +librrd-dev_1.4.7-2_i386 +librsl-dev_1.42-2_i386 +librsskit-dev_0.3-2_i386 +librsvg2-2.0-cil-dev_2.26.0-8_all +librsvg2-dev_2.36.1-2_i386 +librsync-dev_0.9.7-9_i386 +librtai-dev_3.8.1-4_i386 +librtas-dev_1.3.6-1_i386 +librtasevent-dev_1.3.6-1_i386 +librtaudio-dev_4.0.10~ds0-2_i386 +librtfcomp-dev_1.1-5+b1_i386 +librtfilter-dev_1.1-4_i386 +librtmidi-dev_1.0.15~ds0-2_i386 +librtmp-dev_2.4+20111222.git4e06e21-1_i386 +librubberband-dev_1.3-1.3_i386 +librudecgi-dev_5.0.0-1_i386 +libruli4-dev_0.33-1.1_i386 +librxp-dev_1.5.0-1_i386 +libs3-dev_2.0-1_i386 +libs3d-dev_0.2.2-8_i386 +libs3dw-dev_0.2.2-8_i386 +libsaamf3-dev_1.1.4-4.1_i386 +libsackpt3-dev_1.1.4-4.1_i386 +libsaclm3-dev_1.1.4-4.1_i386 +libsaevt3-dev_1.1.4-4.1_i386 +libsage-dev_0.2.0-4.1_i386 +libsalck3-dev_1.1.4-4.1_i386 +libsam-dev_1.4.2-3_i386 +libsamba-credentials-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-hostconfig-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-policy-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-util-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamdb-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsaml2-dev_2.4.3-4_i386 +libsampleicc-dev_1.6.4-1+b1_i386 +libsamplerate-ocaml-dev_0.1.1-1+b3_i386 +libsamplerate0-dev_0.1.8-5_i386 +libsamsg4-dev_1.1.4-4.1_i386 +libsane-dev_1.0.22-7.4_i386 +libsane-extras-dev_1.0.22.2_i386 +libsanlock-dev_2.2-2_i386 +libsary-dev_1:1.2.0-2.1_i386 +libsasl2-dev_2.1.25.dfsg1-6+deb7u1_i386 +libsatmr3-dev_1.1.4-4.1_i386 +libsbjson-dev_2.3.2-2_i386 +libsbsms-dev_2.0.1-1_i386 +libsbuf-dev_9.0+ds1-4_i386 +libsbuild-dev_1.6.4-4_i386 +libsc-dev_2.3.1-14_i386 +libscalapack-mpi-dev_1.8.0-9_i386 +libscalapack-pvm-dev_1.8.0-9_i386 +libscalc-dev_0.2.4-1_i386 +libscamperfile0-dev_20111202b-1_i386 +libschroedinger-dev_1.0.11-2_i386 +libschroedinger-ocaml-dev_0.1.0-1+b3_i386 +libscim-dev_1.4.13-5_i386 +libscm-dev_5e5-3.2_i386 +libscotch-dev_5.1.12b.dfsg-1.2_i386 +libscotchmetis-dev_5.1.12b.dfsg-1.2_i386 +libscotchparmetis-dev_5.1.12b.dfsg-1.2_i386 +libsctp-dev_1.0.11+dfsg-2_i386 +libscythestat-dev_1.0.2-1_all +libsdl-console-dev_2.1-3_i386 +libsdl-gfx1.2-dev_2.0.23-3_i386 +libsdl-image1.2-dev_1.2.12-2_i386 +libsdl-mixer1.2-dev_1.2.12-3_i386 +libsdl-net1.2-dev_1.2.8-2_i386 +libsdl-ocaml-dev_0.9.0-1_i386 +libsdl-pango-dev_0.1.2-6_i386 +libsdl-sge-dev_030809dfsg-3_i386 +libsdl-sound1.2-dev_1.0.3-6_i386 +libsdl-stretch-dev_0.3.1-3_i386 +libsdl-ttf2.0-dev_2.0.11-2_i386 +libsdl1.2-dev_1.2.15-5_i386 +libsdpa-dev_7.3.8+dfsg-1_i386 +libsearchclient-dev_0.7.7-3_i386 +libseaudit-dev_3.3.7-3_i386 +libseed-gtk3-dev_3.2.0-2_i386 +libsefs-dev_3.3.7-3_i386 +libselinux1-dev_2.1.9-5_i386 +libsemanage1-dev_2.1.6-6_i386 +libsensors-applet-plugin-dev_3.0.0-0.2_i386 +libsensors4-dev_1:3.3.2-2+deb7u1_i386 +libsepol1-dev_2.1.4-3_i386 +libserd-dev_0.14.0~dfsg0-2_i386 +libserf-dev_1.1.0-2_i386 +libsexplib-camlp4-dev_7.0.4-2_i386 +libsexy-dev_0.1.11-2+b1_i386 +libsfml-dev_1.6+dfsg2-2_i386 +libsfst1-1.2-0-dev_1.2.0-1.2_i386 +libsgutils2-dev_1.33-1_i386 +libsha-ocaml-dev_1.7-2+b2_i386 +libshairport-dev_1.2.1~git20120110.aeb4987-2_i386 +libshevek-dev_1.3-1_i386 +libshhmsg1-dev_1.4.1-4.1_i386 +libshhopt1-dev_1.1.7-2.1_i386 +libshiboken-dev_1.1.1-1_i386 +libshibsp-dev_2.4.3+dfsg-5+b1_i386 +libshisa-dev_1.0.1-2_i386 +libshishi-dev_1.0.1-2_i386 +libshout-ocaml-dev_0.2.7-1+b3_i386 +libshout3-dev_2.2.2-8_i386 +libshp-dev_1.2.10-7_i386 +libshr-glib-dev_2011.03.08.2~git20110930-2_i386 +libsidl-dev_1.4.0.dfsg-8.1_i386 +libsidplay1-dev_1.36.59-5_i386 +libsidplay2-dev_2.1.1-14_i386 +libsidplayfp-dev_0.3.5-1_i386 +libsidutils-dev_2.1.1-14_i386 +libsieve2-dev_2.2.6-1.1_i386 +libsigc++-1.2-dev_1.2.7-2_i386 +libsigc++-2.0-dev_2.2.10-0.2_i386 +libsigc++-dev_1.0.4-9.4_i386 +libsigrok0-dev_0.1.0-2_i386 +libsigrokdecode0-dev_0.1.0-2_i386 +libsigsegv-dev_2.9-4_i386 +libsilly-dev_0.1.0-3_i386 +libsilo-dev_4.8-13_i386 +libsimage-dev_1.7.0-1.1+b1_i386 +libsimplelist0-dev_0.3.4-2_i386 +libsipwitch-dev_1.2.4-1_i386 +libsiscone-dev_2.0.5-1_i386 +libsiscone-spherical-dev_2.0.5-1_i386 +libskk-dev_0.0.12-3_i386 +libskstream-0.3-dev_0.3.8-1_i386 +libslang2-dev_2.2.4-15_i386 +libslepc3.2-dev_3.2-p5-1_i386 +libslp-dev_1.2.1-9_i386 +libslurm-dev_2.3.4-2+b1_i386 +libslurmdb-dev_2.3.4-2+b1_i386 +libslv2-dev_0.6.6+dfsg1-2_i386 +libsm-dev_2:1.2.1-2_i386 +libsmbclient-dev_2:3.6.6-6+deb7u3_i386 +libsmbclient-raw-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsmbios-dev_2.0.3.dfsg-1.1_i386 +libsmf-dev_1.3-2_i386 +libsmi2-dev_0.4.8+dfsg2-7_i386 +libsmokekde-dev_4:4.8.4-1_i386 +libsmokeqt4-dev_4:4.8.4-1_i386 +libsmpeg-dev_0.4.5+cvs20030824-5_i386 +libsnacc-dev_1.3.1-1_i386 +libsnack2-dev_2.2.10-dfsg1-12.1_i386 +libsnappy-dev_1.0.5-2_i386 +libsndfile1-dev_1.0.25-5_i386 +libsndobj-dev_2.6.6.1-3_i386 +libsnmp-dev_5.4.3~dfsg-2.8_i386 +libsnmpkit-dev_0.9-16_i386 +libsocialweb-client-dev_0.25.20-2.1_i386 +libsocialweb-dev_0.25.20-2.1_i386 +libsocksd0-dev_1.1.19.dfsg-3+b3_i386 +libsofa-c-dev_2012.03.01-1_i386 +libsofa1-dev_1.0~beta4-7_i386 +libsofia-sip-ua-dev_1.12.11+20110422-1_i386 +libsofia-sip-ua-glib-dev_1.12.11+20110422-1_i386 +libsofthsm-dev_1.3.3-2_i386 +libsoil-dev_1.07~20080707.dfsg-2_i386 +libsombok-dev_2.2.1-1_i386 +libsonic-dev_0.1.17-1.1_i386 +libsope-dev_1.3.16-1_i386 +libsoprano-dev_2.7.6+dfsg.1-2wheezy1_i386 +libsoqt4-dev_1.5.0-2_i386 +libsord-dev_0.8.0~dfsg0-1_i386 +libsoundgen-dev_0.6-4_i386 +libsoundtouch-dev_1.6.0-3_i386 +libsoundtouch-ocaml-dev_0.1.7-1+b1_i386 +libsoup-gnome2.4-dev_2.38.1-3_i386 +libsoup2.4-dev_2.38.1-3_i386 +libsoupcutter-dev_1.1.7-1.2_i386 +libsource-highlight-dev_3.1.6-1.1_i386 +libsox-dev_14.4.0-3_i386 +libsp-gxmlcpp-dev_1.0.20040603-5_i386 +libsp1-dev_1.3.4-1.2.1-47.1+b1_i386 +libspandsp-dev_0.0.6~pre20-3.1_i386 +libsparsehash-dev_1.10-1_all +libsparskit-dev_2.0.0-2_i386 +libspatialindex-dev_1.7.0-1_i386 +libspatialite-dev_3.0.0~beta20110817-3+deb7u1_i386 +libspctag-dev_0.2-1_i386 +libspectre-dev_0.2.7-2_i386 +libspectrum-dev_1.0.0-3_i386 +libspeechd-dev_0.7.1-6.2_i386 +libspeex-dev_1.2~rc1-7_i386 +libspeex-ocaml-dev_0.2.0-1+b3_i386 +libspeexdsp-dev_1.2~rc1-7_i386 +libspf2-dev_1.2.9-7_i386 +libsphere-dev_3.2-4_i386 +libspice-client-glib-2.0-dev_0.12-5_i386 +libspice-client-gtk-2.0-dev_0.12-5_i386 +libspice-client-gtk-3.0-dev_0.12-5_i386 +libspice-protocol-dev_0.10.1-1_all +libspice-server-dev_0.11.0-1+deb7u1_i386 +libspiro-dev_20071029-2_i386 +libspnav-dev_0.2.2-1_i386 +libspooles-dev_2.2-9_i386 +libsprng2-dev_2.0a-8_i386 +libsqlexpr-ocaml-dev_0.4.1-1+b5_i386 +libsqlheavy-dev_0.1.1-1_i386 +libsqlheavygtk-dev_0.1.1-1_i386 +libsqlite0-dev_2.8.17-7_i386 +libsqlite3-dev_3.7.13-1+deb7u1_i386 +libsqlite3-ocaml-dev_1.6.1-1+b1_i386 +libsquizz-dev_0.99a-2_i386 +libsratom-dev_0.2.0~dfsg0-1_i386 +libsrecord-dev_1.58-1+b1_i386 +libsrf-dev_0.1+dfsg-1_i386 +libsrtp0-dev_1.4.4+20100615~dfsg-2+deb7u1_i386 +libss7-dev_1.0.2-3_i386 +libsscm-dev_0.8.5-2.1_i386 +libssh-dev_0.5.4-1+deb7u1_i386 +libssh2-1-dev_1.4.2-1.1_i386 +libssl-dev_1.0.1e-2+deb7u7_i386 +libssl-ocaml-dev_0.4.6-1_i386 +libsslcommon2-dev_0.16-6+deb7u1_i386 +libssreflect-ocaml-dev_1.3pl4-1_i386 +libsss-sudo-dev_1.8.4-2_i386 +libst-dev_1.9-3_i386 +libstaden-read-dev_1.12.4-1_i386 +libstarlink-ast-dev_7.0.4+dfsg-1_i386 +libstarlink-pal-dev_0.1.0-1_i386 +libstarpu-dev_1.0.1+dfsg-1_i386 +libstartup-notification0-dev_0.12-1_i386 +libstatgrab-dev_0.17-1_i386 +libstdc++6-4.4-dev_4.4.7-2_i386 +libstdc++6-4.6-dev_4.6.3-14_i386 +libstdc++6-4.7-dev_4.7.2-5_i386 +libstemmer-dev_0+svn546-2_i386 +libsteptalk-dev_0.10.0-5+b1_i386 +libstfl-dev_0.22-1+b1_i386 +libstk0-dev_4.4.3-2_i386 +libstlport4.6-dev_4.6.2-7_i386 +libstonith1-dev_1.0.9+hg2665-1_i386 +libstonithd1-dev_1.1.7-1_i386 +libstreamanalyzer-dev_0.7.7-3_i386 +libstreams-dev_0.7.7-3_i386 +libstrigihtmlgui-dev_0.7.7-3_i386 +libstrigiqtdbusclient-dev_0.7.7-3_i386 +libstroke0-dev_0.5.1-6_i386 +libstxxl-dev_1.3.1-4_i386 +libsublime-dev_1.3.1-2_i386 +libsubtitleeditor-dev_0.33.0-1_i386 +libsubunit-dev_0.0.8+bzr176-1_i386 +libsugarext-dev_0.96.1-2_i386 +libsuil-dev_0.6.4~dfsg0-3_i386 +libsuitesparse-dev_1:3.4.0-3_i386 +libsundials-serial-dev_2.5.0-3_i386 +libsunpinyin-dev_2.0.3+git20120607-1_i386 +libsuperlu3-dev_3.0+20070106-3_i386 +libsvga1-dev_1:1.4.3-33_i386 +libsvm-dev_3.12-1_i386 +libsvn-dev_1.6.17dfsg-4+deb7u6_i386 +libsvncpp-dev_0.12.0dfsg-6_i386 +libsvnqt-dev_1.5.5-4.1_i386 +libsvrcore-dev_1:4.0.4-15_i386 +libswami-dev_2.0.0+svn389-2_i386 +libswe-dev_1.77.00.0005-2_i386 +libswiften-dev_2.0~beta1+dev47-1_i386 +libsword-dev_1.6.2+dfsg-5_i386 +libswscale-dev_6:0.8.10-1_i386 +libsx-dev_2.05-3_i386 +libsyfi1.0-dev_1.0.0.dfsg-1_i386 +libsylph-dev_1.1.0-8_i386 +libsymmetrica-dev_2.0-1_i386 +libsynce0-dev_0.15-1.1_i386 +libsyncml-dev_0.5.4-2.1_i386 +libsynfig-dev_0.63.05-1_i386 +libsynopsis0.12-dev_0.12-8_i386 +libsynthesis-dev_3.4.0.16.7-1_i386 +libsysactivity-dev_0.6.4-1_i386 +libsysfs-dev_2.1.0+repack-2_i386 +libsyslog-ng-dev_3.3.5-4_i386 +libsyslog-ocaml-dev_1.4-6+b2_i386 +libsystemd-daemon-dev_44-11+deb7u4_i386 +libsystemd-id128-dev_44-11+deb7u4_i386 +libsystemd-journal-dev_44-11+deb7u4_i386 +libsystemd-login-dev_44-11+deb7u4_i386 +libt1-dev_5.1.2-3.6_i386 +libtacacs+1-dev_4.0.4.19-11_all +libtachyon-dev_0.99~b2+dfsg-0.4_i386 +libtag-extras-dev_1.0.1-3_i386 +libtag1-dev_1.7.2-1_i386 +libtagc0-dev_1.7.2-1_i386 +libtagcoll2-dev_2.0.13-1.1_i386 +libtaglib-cil-dev_2.0.4.0-1_all +libtaglib-ocaml-dev_0.2.0-1+b1_i386 +libtaktuk-1-dev_3.7.4-1_i386 +libtalloc-dev_2.0.7+git20120207-1_i386 +libtamuanova-dev_0.2-2_i386 +libtango7-dev_7.2.6+dfsg-14_i386 +libtaningia-dev_0.2.2-1_i386 +libtaoframework-devil-cil-dev_2.1.svn20090801-9_all +libtaoframework-ffmpeg-cil-dev_2.1.svn20090801-9_all +libtaoframework-freeglut-cil-dev_2.1.svn20090801-9_all +libtaoframework-freetype-cil-dev_2.1.svn20090801-9_all +libtaoframework-ftgl-cil-dev_2.1.svn20090801-9_all +libtaoframework-lua-cil-dev_2.1.svn20090801-9_all +libtaoframework-ode-cil-dev_2.1.svn20090801-9_all +libtaoframework-openal-cil-dev_2.1.svn20090801-9_all +libtaoframework-opengl-cil-dev_2.1.svn20090801-9_all +libtaoframework-physfs-cil-dev_2.1.svn20090801-9_all +libtaoframework-sdl-cil-dev_2.1.svn20090801-9_all +libtar-dev_1.2.16-1+deb7u2_i386 +libtarantool-dev_1.4.6+20120629+2158-1_i386 +libtasn1-3-dev_2.13-2_i386 +libtbb-dev_4.0+r233-1_i386 +libtcc-dev_0.9.26~git20120612.ad5f375-6_i386 +libtcd-dev_2.2.2-1_i386 +libtclap-dev_1.2.1-1_i386 +libtclcl1-dev_1.20-6_i386 +libtdb-dev_1.2.10-2_i386 +libtecla1-dev_1.6.1-5_i386 +libteem-dev_1.11.0~svn5226-1_i386 +libtelepathy-farstream-dev_0.4.0-3_i386 +libtelepathy-glib-dev_0.18.2-2_i386 +libtelepathy-logger-dev_0.4.0-1_i386 +libtelepathy-logger-qt4-dev_0.4.0-1_i386 +libtelepathy-qt4-dev_0.9.1-4_i386 +libtelnet-dev_0.21-1_i386 +libtemplates-parser11.6-dev_11.6-2_i386 +libterralib-dev_4.0.0-4_i386 +libtesseract-dev_3.02.01-6_i386 +libtevent-dev_0.9.16-1_i386 +libtext-ocaml-dev_0.5-1+b2_i386 +libtextwrap-dev_0.1-13_i386 +libthai-dev_0.1.18-2_i386 +libtheora-dev_1.1.1+dfsg.1-3.1_i386 +libtheora-ocaml-dev_0.3.0-1+b3_i386 +libthepeg-dev_1.8.0-1_i386 +libthrust-dev_1.6.0-1_all +libthunar-vfs-1-dev_1.2.0-3+b1_i386 +libthunarx-2-dev_1.2.3-4+b1_i386 +libticables-dev_1.2.0-2_i386 +libticalcs-dev_1.1.3+dfsg1-1_i386 +libticonv-dev_1.1.0-1.1_i386 +libtidy-dev_20091223cvs-1.2_i386 +libtiff4-dev_3.9.6-11_i386 +libtiff5-alt-dev_4.0.2-6+deb7u2_i386 +libtiff5-dev_4.0.2-6+deb7u2_i386 +libtifiles-dev_1.1.1-1_i386 +libtimbl3-dev_6.4.2-1_i386 +libtimblserver2-dev_1.4-2_i386 +libtinfo-dev_5.9-10_i386 +libtinyxml-dev_2.6.2-1_i386 +libtinyxml2-dev_0~git20120518.1.a2ae54e-1_i386 +libtirpc-dev_0.2.2-5_i386 +libtk-img-dev_1:1.3-release-12_i386 +libtnt-dev_1.2.6-1_all +libtntdb-dev_1.2-2+b1_i386 +libtntnet-dev_2.1-2+deb7u1_i386 +libtododb-dev_0.11-3_i386 +libtogl-dev_1.7-12_all +libtokyocabinet-dev_1.4.47-2_i386 +libtokyotyrant-dev_1.1.40-4.1+b1_i386 +libtolua++5.1-dev_1.0.93-3_i386 +libtolua-dev_5.2.0-1_i386 +libtomcrypt-dev_1.17-3.2_i386 +libtommath-dev_0.42.0-1_i386 +libtomoe-dev_0.6.0-1.3_i386 +libtonezone-dev_1:2.5.0.1-2_i386 +libtophide-ocaml-dev_1.0.0-3_all +libtorch3-dev_3.1-2.1_i386 +libtorque2-dev_2.4.16+dfsg-1+deb7u2_i386 +libtorrent-dev_0.13.2-1_i386 +libtorrent-rasterbar-dev_0.15.10-1+b1_i386 +libtorture-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libtotem-dev_3.0.1-8_i386 +libtotem-pg-dev_1.4.2-3_i386 +libtotem-plparser-dev_3.4.2-1_i386 +libtowitoko-dev_2.0.7-8.3_i386 +libtpl-dev_1.5-2_all +libtpm-unseal-dev_1.3.7-1_i386 +libtrace3-dev_3.0.14-1_i386 +libtracker-extract-0.14-dev_0.14.1-3_i386 +libtracker-miner-0.14-dev_0.14.1-3_i386 +libtracker-sparql-0.14-dev_0.14.1-3_i386 +libtransitioner1-dev_1.1.7-1_i386 +libtre-dev_0.8.0-3_i386 +libtreil-dev_1.8-1.1_i386 +libts-dev_1.0-11_i386 +libtse3-dev_0.3.1-4.3_i386 +libtsk-dev_3.2.3-2_i386 +libtspi-dev_0.3.9-3+wheezy1_i386 +libtulip-dev_3.7.0dfsg-4_i386 +libtumbler-1-dev_0.1.25-1+b1_i386 +libtut-dev_0.0.20070706-1_all +libtuxcap-dev_1.4.0.dfsg2-2.1_i386 +libtwin-dev_12.04.13.17.57-g130ee5f-2_i386 +libtwofish-dev_0.3-3_i386 +libtwolame-dev_0.3.13-1_i386 +libtxc-dxtn-s2tc-dev_0~git20110809-3_i386 +libtype-conv-camlp4-dev_3.0.4-1_i386 +libtyxml-ocaml-dev_2.1-1_i386 +libuchardet-dev_0.0.1-1_i386 +libucimf-dev_2.3.8-4_i386 +libucl-dev_1.03-5_i386 +libuclmmbase1-dev_1.2.16.0-1_i386 +libucommon-dev_5.2.2-4_i386 +libucto1-dev_0.5.2-2_i386 +libudev-dev_175-7.2_i386 +libudf-dev_0.83-4_i386 +libudt-dev_4.10+dfsg-1_i386 +libudunits2-dev_2.1.23-3_i386 +libuhd-dev_3.4.2-1_i386 +libuim-dev_1:1.8.1-4_i386 +libumlib-dev_0.8.2-1_i386 +libunac1-dev_1.8.0-6_i386 +libunbound-dev_1.4.17-3_i386 +libunicap2-dev_0.9.12-2_i386 +libuninameslist-dev_0.0.20091231-1.1_i386 +libuninum-dev_2.7-1.1_i386 +libunique-3.0-dev_3.0.2-1_i386 +libunique-dev_1.1.6-4_i386 +libunistring-dev_0.9.3-5_i386 +libunittest++-dev_1.4.0-3_i386 +libunshield-dev_0.6-3_i386 +libunwind-setjmp0-dev_0.99-0.3_i386 +libunwind7-dev_0.99-0.3_i386 +libupnp-dev_1:1.6.17-1.2_all +libupnp4-dev_1.8.0~svn20100507-1.2_i386 +libupnp6-dev_1:1.6.17-1.2_i386 +libupower-glib-dev_0.9.17-1_i386 +libupsclient1-dev_2.6.4-2.3+deb7u1_i386 +libupse-dev_1.0.0-1_i386 +libuptimed-dev_1:0.3.17-3.1_i386 +liburcu-dev_0.6.7-2_i386 +liburfkill-glib-dev_0.3.0-1_i386 +liburg0-dev_0.8.12-4_i386 +liburiparser-dev_0.7.5-1_i386 +libusb++-dev_2:0.1.12-20+nmu1_i386 +libusb-1.0-0-dev_2:1.0.11-1_i386 +libusb-dev_2:0.1.12-20+nmu1_i386 +libusb-ocaml-dev_1.2.0-2+b7_i386 +libusbip-dev_1.1.1+3.2.17-1_i386 +libusbmuxd-dev_1.0.7-2_i386 +libusbprog-dev_0.2.0-2_i386 +libusbredirhost-dev_0.4.3-2_i386 +libusbredirparser-dev_0.4.3-2_i386 +libusbtc08-dev_1.7.2-1_i386 +libuser1-dev_1:0.56.9.dfsg.1-1.2_i386 +libust-dev_2.0.4-1_i386 +libustr-dev_1.0.4-3_i386 +libutempter-dev_1.1.5-4_i386 +libuu-dev_0.5.20-3.3_i386 +libuuidm-ocaml-dev_0.9.4-1_i386 +libv4l-dev_0.8.8-3_i386 +libv8-dev_3.8.9.20-2_i386 +libv8-i18n-dev_0~0.svn7-3_i386 +libva-dev_1.0.15-4_i386 +libvala-0.14-dev_0.14.2-2_i386 +libvala-0.16-dev_0.16.1-2_i386 +libvaladoc-dev_0.3.2~git20120227-1_i386 +libvalhalla-dev_2.0.0-4+b1_i386 +libvanessa-adt-dev_0.0.9-1_i386 +libvanessa-logger-dev_0.0.10-1.1_i386 +libvanessa-socket-dev_0.0.12-1_i386 +libvarconf-dev_0.6.7-2_i386 +libvarnishapi-dev_3.0.2-2+deb7u1_i386 +libvbr-dev_2.6.8-4_i386 +libvc-dev_003.dfsg.1-12_i386 +libvcdinfo-dev_0.7.24+dfsg-0.1_i386 +libvde-dev_2.3.2-4_i386 +libvdeplug-dev_2.3.2-4_i386 +libvdk2-dev_2.4.0-5.3_i386 +libvdkbuilder2-dev_2.4.0-4.3_i386 +libvdkxdb2-dev_2.4.0-3.4_i386 +libvdpau-dev_0.4.1-7_i386 +libventrilo-dev_1.2.4-1_i386 +libverbiste-dev_0.1.34-1_i386 +libverto-dev_0.2.2-1_i386 +libvformat-dev_1.13-10_i386 +libvia-dev_2.0.4-2_i386 +libvibrant6-dev_6.1.20120620-2_i386 +libview-dev_0.6.6-2.1_i386 +libvigraimpex-dev_1.7.1+dfsg1-3_i386 +libvips-dev_7.28.5-1+deb7u1_i386 +libvirt-dev_0.9.12.3-1_i386 +libvirt-glib-1.0-dev_0.0.8-1_i386 +libvirt-ocaml-dev_0.6.1.2-1_i386 +libvisca-dev_1.0.1-1_i386 +libvisio-dev_0.0.17-1_i386 +libvisual-0.4-dev_0.4.0-5_i386 +libvlc-dev_2.0.3-5_i386 +libvlccore-dev_2.0.3-5_i386 +libvmmlib-dev_1.0-2_all +libvncserver-dev_0.9.9+dfsg-1_i386 +libvo-aacenc-dev_0.1.2-1_i386 +libvo-amrwbenc-dev_0.1.2-1_i386 +libvoaacenc-ocaml-dev_0.1.0-1+b1_i386 +libvoikko-dev_3.5-1.1_i386 +libvolpack1-dev_1.0b3-3_i386 +libvorbis-dev_1.3.2-1.3_i386 +libvorbis-ocaml-dev_0.6.1-1+b1_i386 +libvorbisidec-dev_1.0.2+svn18153-0.2_i386 +libvotequorum-dev_1.4.2-3_i386 +libvpb-dev_4.2.55-1_i386 +libvpx-dev_1.1.0-1_i386 +libvrb0-dev_0.5.1-5.1_i386 +libvte-2.90-dev_1:0.32.2-1_i386 +libvte-dev_1:0.28.2-5_i386 +libvte0.16-cil-dev_2.26.0-8_i386 +libvtk5-dev_5.8.0-13+b1_i386 +libvtk5-qt4-dev_5.8.0-13+b1_i386 +libvtkedge-dev_0.2.0~20110819-2_i386 +libvtkgdcm2-dev_2.2.0-14.1_i386 +libvxl1-dev_1.14.0-18_i386 +libwacom-dev_0.6-1_i386 +libwaei-dev_3.4.3-1_i386 +libwaili-dev_19990723-20_i386 +libwavefront-standalone3.0-dev_3.0.2+dfsg-4+b1_i386 +libwavpack-dev_4.60.1-3_i386 +libwayland-dev_0.85.0-2_i386 +libwbclient-dev_2:3.6.6-6+deb7u3_i386 +libwbxml2-dev_0.10.7-1_i386 +libwcstools-dev_3.8.5-1_i386 +libwebauth-dev_4.1.1-2_i386 +libwebcam0-dev_0.2.2-1_i386 +libwebkit-cil-dev_0.3-6_all +libwebkit-dev_1.8.1-3.4_i386 +libwebkitgtk-3.0-dev_1.8.1-3.4_i386 +libwebkitgtk-dev_1.8.1-3.4_i386 +libwebp-dev_0.1.3-3+nmu1_i386 +libwebrtc-audio-processing-dev_0.1-2_i386 +libweed-dev_1.6.2~ds1-2_i386 +libwfmath-0.3-dev_0.3.12-3_i386 +libwfut-0.2-dev_0.2.1-2_i386 +libwibble-dev_0.1.28-1.1_i386 +libwildmidi-dev_0.2.3.4-2.1_i386 +libwine-dev_1.4.1-4_i386 +libwings-dev_0.95.3-2_i386 +libwireshark-dev_1.8.2-5wheezy10_i386 +libwiretap-dev_1.8.2-5wheezy10_i386 +libwmf-dev_0.2.8.4-10.3_i386 +libwnck-3-dev_3.4.2-1_i386 +libwnck-dev_2.30.7-1_i386 +libwnck1.0-cil-dev_2.26.0-8_i386 +libwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libwnn6-dev_1.0.0-14.2+b1_i386 +libwpd-dev_0.9.4-3_i386 +libwpg-dev_0.2.1-1_i386 +libwps-dev_0.2.7-1_i386 +libwrap0-dev_7.6.q-24_i386 +libwraster3-dev_0.95.3-2_i386 +libwreport-dev_2.4-1_i386 +libwsutil-dev_1.8.2-5wheezy10_i386 +libwt-dev_3.2.1-2_i386 +libwtdbo-dev_3.2.1-2_i386 +libwtdbofirebird-dev_3.2.1-2_i386 +libwtdbopostgres-dev_3.2.1-2_i386 +libwtdbosqlite-dev_3.2.1-2_i386 +libwtext-dev_3.2.1-2_i386 +libwtfcgi-dev_3.2.1-2_i386 +libwthttp-dev_3.2.1-2_i386 +libwttest-dev_3.2.1-2_i386 +libwv-dev_1.2.9-3_i386 +libwv2-dev_0.4.2.dfsg.2-1~deb7u1_i386 +libwvstreams-dev_4.6.1-5_i386 +libwxbase2.8-dev_2.8.12.1-12_i386 +libwxgtk2.8-dev_2.8.12.1-12_i386 +libwxsmithlib-dev_10.05-2.1_i386 +libwxsmithlib0-dev_10.05-2.1_i386 +libwxsqlite3-2.8-dev_3.0.0.1~dfsg0-2_i386 +libwxsvg-dev_2:1.1.8~dfsg0-2_i386 +libx11-dev_2:1.5.0-1+deb7u1_i386 +libx11-xcb-dev_2:1.5.0-1+deb7u1_i386 +libx264-dev_2:0.123.2189+git35cf912-1_i386 +libx52pro-dev_0.1.1-2.1_i386 +libx86-dev_1.1+ds1-10_i386 +libxalan110-dev_1.10-6_i386 +libxapian-dev_1.2.12-2_i386 +libxatracker-dev_8.0.5-4+deb7u2_i386 +libxau-dev_1:1.0.7-1_i386 +libxaw7-dev_2:1.0.10-2_i386 +libxbae-dev_4.60.4-3_i386 +libxbase2.0-dev_2.0.0-8.5_i386 +libxcb-composite0-dev_1.8.1-2+deb7u1_i386 +libxcb-damage0-dev_1.8.1-2+deb7u1_i386 +libxcb-dpms0-dev_1.8.1-2+deb7u1_i386 +libxcb-dri2-0-dev_1.8.1-2+deb7u1_i386 +libxcb-ewmh-dev_0.3.9-2_i386 +libxcb-glx0-dev_1.8.1-2+deb7u1_i386 +libxcb-icccm4-dev_0.3.9-2_i386 +libxcb-image0-dev_0.3.9-1_i386 +libxcb-keysyms1-dev_0.3.9-1_i386 +libxcb-randr0-dev_1.8.1-2+deb7u1_i386 +libxcb-record0-dev_1.8.1-2+deb7u1_i386 +libxcb-render-util0-dev_0.3.8-1.1_i386 +libxcb-render0-dev_1.8.1-2+deb7u1_i386 +libxcb-res0-dev_1.8.1-2+deb7u1_i386 +libxcb-screensaver0-dev_1.8.1-2+deb7u1_i386 +libxcb-shape0-dev_1.8.1-2+deb7u1_i386 +libxcb-shm0-dev_1.8.1-2+deb7u1_i386 +libxcb-sync0-dev_1.8.1-2+deb7u1_i386 +libxcb-util0-dev_0.3.8-2_i386 +libxcb-xevie0-dev_1.8.1-2+deb7u1_i386 +libxcb-xf86dri0-dev_1.8.1-2+deb7u1_i386 +libxcb-xfixes0-dev_1.8.1-2+deb7u1_i386 +libxcb-xinerama0-dev_1.8.1-2+deb7u1_i386 +libxcb-xprint0-dev_1.8.1-2+deb7u1_i386 +libxcb-xtest0-dev_1.8.1-2+deb7u1_i386 +libxcb-xv0-dev_1.8.1-2+deb7u1_i386 +libxcb-xvmc0-dev_1.8.1-2+deb7u1_i386 +libxcb1-dev_1.8.1-2+deb7u1_i386 +libxcomp-dev_3.5.0.12-1+b1_i386 +libxcomposite-dev_1:0.4.3-2_i386 +libxcp-ocaml-dev_0.5.2-3+b1_i386 +libxcrypt-dev_1:2.4-3_i386 +libxcursor-dev_1:1.1.13-1+deb7u1_i386 +libxdamage-dev_1:1.1.3-2_i386 +libxdb-dev_1.2.0-7.2_i386 +libxdelta2-dev_1.1.3-9_i386 +libxdffileio-dev_0.3-1_i386 +libxdg-basedir-dev_1.1.1-2_i386 +libxdmcp-dev_1:1.1.1-1_i386 +libxdmf-dev_2.1.dfsg.1-5_i386 +libxdo-dev_1:2.20100701.2961-3+deb7u3_i386 +libxen-dev_4.1.4-3+deb7u1_i386 +libxen-ocaml-dev_4.1.4-3+deb7u1_i386 +libxenapi-ocaml-dev_1.3.2-15_i386 +libxenomai-dev_2.6.0-2_i386 +libxerces-c-dev_3.1.1-3_i386 +libxerces-c2-dev_2.8.0+deb1-3_i386 +libxext-dev_2:1.3.1-2+deb7u1_i386 +libxfce4menu-0.1-dev_4.6.2-1_i386 +libxfce4ui-1-dev_4.8.1-1_i386 +libxfce4util-dev_4.8.2-1_i386 +libxfcegui4-dev_4.8.1-5_i386 +libxfconf-0-dev_4.8.1-1_i386 +libxfixes-dev_1:5.0-4+deb7u1_i386 +libxfont-dev_1:1.4.5-3_i386 +libxft-dev_2.3.1-1_i386 +libxi-dev_2:1.6.1-1+deb7u1_i386 +libxine-dev_1.1.21-1+deb7u1_i386 +libxine2-dev_1.2.2-5_i386 +libxinerama-dev_2:1.1.2-1+deb7u1_i386 +libxkbfile-dev_1:1.0.8-1_i386 +libxklavier-dev_5.2.1-1_i386 +libxml++2.6-dev_2.34.2-1_i386 +libxml-light-ocaml-dev_2.2-15_i386 +libxml-security-c-dev_1.6.1-5+deb7u2_i386 +libxml2-dev_2.8.0+dfsg1-7+nmu3_i386 +libxmlada4.1-dev_4.1-2_i386 +libxmlezout2-dev_1.06.1-5_i386 +libxmlm-ocaml-dev_1.1.0-1_i386 +libxmlplaylist-ocaml-dev_0.1.3-1+b2_i386 +libxmlrpc-c++4-dev_1.16.33-3.2_i386 +libxmlrpc-c3-dev_1.16.33-3.2_all +libxmlrpc-core-c3-dev_1.16.33-3.2_i386 +libxmlrpc-epi-dev_0.54.2-1_i386 +libxmlrpc-light-ocaml-dev_0.6.1-3+b5_i386 +libxmlsec1-dev_1.2.18-2_i386 +libxmltok1-dev_1.2-3_i386 +libxmltooling-dev_1.4.2-5_i386 +libxmmsclient++-dev_0.8+dfsg-4_i386 +libxmmsclient++-glib-dev_0.8+dfsg-4_i386 +libxmmsclient-dev_0.8+dfsg-4_i386 +libxmmsclient-glib-dev_0.8+dfsg-4_i386 +libxmpi4-dev_2.2.3b8-13_i386 +libxmu-dev_2:1.1.1-1_i386 +libxmuu-dev_2:1.1.1-1_i386 +libxnee-dev_3.13-1_i386 +libxneur-dev_0.15.0-1.1_i386 +libxosd-dev_2.2.14-2_i386 +libxp-dev_1:1.0.1-2+deb7u1_i386 +libxpa-dev_2.1.14-2_i386 +libxplc0.3.13-dev_0.3.13-3_i386 +libxpm-dev_1:3.5.10-1_i386 +libxqdbm-dev_1.8.78-2_i386 +libxqilla-dev_2.3.0-1_i386 +libxr1-dev_1.0-2.1_i386 +libxrandr-dev_2:1.3.2-2+deb7u1_i386 +libxrender-dev_1:0.9.7-1+deb7u1_i386 +libxres-dev_2:1.0.6-1+deb7u1_i386 +libxsettings-client-dev_0.17-6_i386 +libxsettings-dev_0.11-3_i386 +libxslt1-dev_1.1.26-14.1_i386 +libxss-dev_1:1.2.2-1_i386 +libxstr-ocaml-dev_0.2.1-21+b3_i386 +libxstrp4-camlp4-dev_1.8-3_all +libxt-dev_1:1.1.3-1+deb7u1_i386 +libxtst-dev_2:1.2.1-1+deb7u1_i386 +libxv-dev_2:1.0.7-1+deb7u1_i386 +libxvidcore-dev_2:1.3.2-9_i386 +libxvmc-dev_2:1.0.7-1+deb7u2_i386 +libxxf86dga-dev_2:1.1.3-2+deb7u1_i386 +libxxf86vm-dev_1:1.1.2-1+deb7u1_i386 +libxy-dev_0.8-1+b1_i386 +libyahoo2-dev_1.0.1-1_i386 +libyajl-dev_2.0.4-2_i386 +libyaml-cpp-dev_0.3.0-1_i386 +libyaml-dev_0.1.4-2+deb7u4_i386 +libyaz4-dev_4.2.30-2_i386 +libyelp-dev_3.4.2-1+b1_i386 +libygl4-dev_4.2e-4_i386 +libykclient-dev_2.6-1_i386 +libykpers-1-dev_1.7.0-1_i386 +libyojson-ocaml-dev_1.0.3-1_i386 +libytnef0-dev_1.5-4_i386 +libyubikey-dev_1.8-1_i386 +libz80ex-dev_1.1.19-3_i386 +libzarith-ocaml-dev_1.1-2_i386 +libzbar-dev_0.10+doc-8_i386 +libzbargtk-dev_0.10+doc-8_i386 +libzbarqt-dev_0.10+doc-8_i386 +libzeep-dev_2.9.0-2_i386 +libzeitgeist-cil-dev_0.8.0.0-4_all +libzeitgeist-dev_0.3.18-1_i386 +libzen-dev_0.4.27-2_i386 +libzephyr-dev_3.0.2-2_i386 +libzerg0-dev_1.0.7-3_i386 +libzeroc-ice34-dev_3.4.2-8.2_i386 +libzinnia-dev_0.06-1+b1_i386 +libzip-dev_0.10.1-1.1_i386 +libzip-ocaml-dev_1.04-6+b3_i386 +libzipios++-dev_0.1.5.9+cvs.2007.04.28-5.1_i386 +libzita-alsa-pcmi-dev_0.2.0-1_i386 +libzita-convolver-dev_3.1.0-2_i386 +libzita-resampler-dev_1.1.0-3_i386 +libzlcore-dev_0.12.10dfsg-8_i386 +libzltext-dev_0.12.10dfsg-8_i386 +libzmq-dev_2.2.0+dfsg-2_i386 +libzn-poly-dev_0.8-1.1_i386 +libzookeeper-mt-dev_3.3.5+dfsg1-2_i386 +libzookeeper-st-dev_3.3.5+dfsg1-2_i386 +libzorp-dev_3.9.5-4_i386 +libzorpll-dev_3.9.1.3-1_i386 +libzrtpcpp-dev_2.0.0-3_i386 +libzthread-dev_2.3.2-7_i386 +libzvbi-dev_0.2.33-6_i386 +libzzip-dev_0.13.56-1.1_i386 +licq-dev_1.6.1-3_all +linux-libc-dev_3.2.57-3_i386 +lldpad-dev_0.9.44-1_i386 +llvm-2.9-dev_2.9+dfsg-7_i386 +llvm-3.0-dev_3.0-10_i386 +llvm-3.1-dev_3.1-1_i386 +llvm-dev_1:3.0-14+nmu2_i386 +lttv-dev_0.12.38-21032011-1+b1_i386 +lua-apr-dev_0.23.2-1_i386 +lua-bitop-dev_1.0.2-1_i386 +lua-curl-dev_0.3.0-7_i386 +lua-curses-dev_5.1.19-2_i386 +lua-cyrussasl-dev_1.0.0-4_i386 +lua-dbi-mysql-dev_0.5+svn78-4_i386 +lua-dbi-postgresql-dev_0.5+svn78-4_i386 +lua-dbi-sqlite3-dev_0.5+svn78-4_i386 +lua-event-dev_0.4.1-2_i386 +lua-expat-dev_1.2.0-5+deb7u1_i386 +lua-filesystem-dev_1.5.0+16+g84f1af5-1_i386 +lua-iconv-dev_7-1_i386 +lua-ldap-dev_1.1.0-1-geeac494-3_i386 +lua-leg-dev_0.1.2-8_all +lua-lgi-dev_0.6.2-1_i386 +lua-lpeg-dev_0.10.2-5_i386 +lua-md5-dev_1.1.2-6_i386 +lua-penlight-dev_1.0.2+htmldoc-2_all +lua-posix-dev_5.1.19-2_i386 +lua-rex-onig-dev_2.6.0-2_i386 +lua-rex-pcre-dev_2.6.0-2_i386 +lua-rex-posix-dev_2.6.0-2_i386 +lua-rex-tre-dev_2.6.0-2_i386 +lua-rings-dev_1.2.3-1_i386 +lua-sec-dev_0.4.1-1_i386 +lua-socket-dev_2.0.2-8_i386 +lua-sql-mysql-dev_2.3.0-1+build0_i386 +lua-sql-postgres-dev_2.3.0-1+build0_i386 +lua-sql-sqlite3-dev_2.3.0-1+build0_i386 +lua-svn-dev_0.4.0-7_i386 +lua-wsapi-fcgi-dev_1.5-3_i386 +lua-zip-dev_1.2.3-11_i386 +lua-zlib-dev_0.2-1_i386 +lua5.1-policy-dev_33_all +lv2-dev_1.0.0~dfsg2-2_i386 +lxc-dev_0.8.0~rc1-8+deb7u2_i386 +lzma-dev_9.22-2_all +manpages-de-dev_1.2-1_all +manpages-dev_3.44-1_all +manpages-fr-dev_3.44d1p1-1_all +manpages-ja-dev_0.5.0.0.20120606-1_all +manpages-pl-dev_1:0.3-1_all +manpages-pt-dev_20040726-4_all +matlab-support-dev_0.0.18_all +mdbtools-dev_0.7-1+deb7u1_i386 +med-bio-dev_1.13.2_all +med-imaging-dev_1.13.2_all +mesa-common-dev_8.0.5-4+deb7u2_i386 +mffm-fftw-dev_1.7-3_i386 +mffm-timecode-dev_1.6-2_all +mingw-w64-dev_2.0.3-1_all +mingw-w64-i686-dev_2.0.3-1_all +mingw-w64-x86-64-dev_2.0.3-1_all +minpack-dev_19961126+dfsg1-1_i386 +mongodb-dev_1:2.0.6-1.1_i386 +mpi-default-dev_1.0.1_i386 +muroard-dev_0.1.10-2_i386 +neko-dev_1.8.1-6_all +nettle-dev_2.4-3_i386 +network-manager-dev_0.9.4.0-10_i386 +ntfs-3g-dev_1:2012.1.15AR.5-2.1_i386 +ocfs2-tools-dev_1.6.4-1+deb7u1_i386 +ocl-icd-dev_1.3-3_i386 +ocl-icd-opencl-dev_1.3-3_i386 +ocsigen-dev_1.3.4-2_all +octave-pkg-dev_1.0.2_all +ofono-dev_1.6-2_all +okteta-dev_4:4.8.4+dfsg-1_i386 +okular-dev_4:4.8.4-3_i386 +open-vm-tools-dev_2:8.8.0+2012.05.21-724730-1+nmu2_all +openais-dev_1.1.4-4.1_i386 +openbox-dev_3.5.0-7_i386 +openchangeserver-dev_1:1.0-3_i386 +oss4-dev_4.2-build2006-2+deb7u1_all +pacemaker-dev_1.1.7-1_i386 +packaging-dev_0.4_all +paraview-dev_3.14.1-6_i386 +parole-dev_0.2.0.6-1+b1_i386 +parser3-dev_3.4.2-2_i386 +pbs-drmaa-dev_1.0.10-3_i386 +petsc-dev_3.2.dfsg-6_all +php5-dev_5.4.4-14+deb7u9_i386 +pidgin-dev_2.10.9-1~deb7u1_all +pinball-dev_0.3.1-13.1_i386 +planetpenguin-racer-gimp-dev_0.4-5_all +planner-dev_0.14.6-1_i386 +plplot-tcl-dev_5.9.9-5_i386 +plptools-dev_1.0.9-2.4_i386 +plymouth-dev_0.8.5.1-5_i386 +portaudio19-dev_19+svn20111121-1_i386 +postfix-dev_2.9.6-2_all +ppl-dev_0.11.2-8_i386 +ppp-dev_2.4.5-5.1_all +prayer-templates-dev_1.3.4-dfsg1-1_i386 +proftpd-dev_1.3.4a-5+deb7u1_i386 +pslib-dev_0.4.5-3_i386 +publib-dev_0.40-1_i386 +puredata-dev_0.43.2-5_all +pvm-dev_3.4.5-12.5_i386 +pxlib-dev_0.6.5-1_i386 +python-all-dev_2.7.3-4+deb7u1_all +python-apt-dev_0.8.8.2_all +python-cairo-dev_1.8.8-1_all +python-cxx-dev_6.2.4-3_all +python-dbus-dev_1.1.1-1_all +python-dev_2.7.3-4+deb7u1_all +python-egenix-mx-base-dev_3.2.1-1.1_all +python-gamera-dev_3.3.3-2_all +python-gi-dev_3.2.2-2_all +python-gnome2-desktop-dev_2.32.0+dfsg-2_all +python-gnome2-dev_2.28.1+dfsg-1_all +python-gnome2-extras-dev_2.25.3-12_all +python-gobject-2-dev_2.28.6-10_all +python-gobject-dev_3.2.2-2_all +python-greenlet-dev_0.3.1-2.5_i386 +python-gst0.10-dev_0.10.22-3_i386 +python-gtk2-dev_2.24.0-3_all +python-kde4-dev_4:4.8.4-1_all +python-ldb-dev_1:1.1.6-1_i386 +python-openturns-dev_1.0-4_i386 +python-pyorbit-dev_2.24.0-6_all +python-qt4-dev_4.9.3-4_all +python-sip-dev_4.13.3-2_i386 +python-talloc-dev_2.0.7+git20120207-1_i386 +python-webkit-dev_1.1.8-2_all +python2.6-dev_2.6.8-1.1_i386 +python2.7-dev_2.7.3-6+deb7u2_i386 +python3-all-dev_3.2.3-6_all +python3-cairo-dev_1.10.0+dfsg-2_all +python3-cxx-dev_6.2.4-3_all +python3-dev_3.2.3-6_all +python3-sip-dev_4.13.3-2_i386 +python3.2-dev_3.2.3-7_i386 +qmf-dev_1.0.7~2011w23.2-2.1_i386 +qtmobility-dev_1.2.0-3_i386 +r-base-dev_2.15.1-4_all +regina-normal-dev_4.93-1_i386 +resource-agents-dev_1:3.9.2-5+deb7u2_i386 +rhythmbox-dev_2.97-2.1_i386 +rivet-plugins-dev_1.8.0-1_all +roarplaylistd-dev_0.1.1-2_all +robot-player-dev_3.0.2+dfsg-4_all +ruby-dev_1:1.9.3_all +ruby-gnome2-dev_1.1.3-2+b1_i386 +ruby1.8-dev_1.8.7.358-7.1+deb7u1_i386 +ruby1.9.1-dev_1.9.3.194-8.1+deb7u2_i386 +rygel-1.0-dev_0.14.3-2+deb7u1_i386 +samba4-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +sbnc-php-dev_1.2-26_all +science-astronomy-dev_1.0_all +science-dataacquisition-dev_1.0_all +science-engineering-dev_1.0_all +science-highenergy-physics-dev_1.0_all +science-mathematics-dev_1.0_all +science-meteorology-dev_1.0_all +science-nanoscale-physics-dev_1.0_all +science-physics-dev_1.0_all +scim-dev_1.4.13-5_all +sciplot-dev_1.36-15_i386 +selinux-policy-dev_2:2.20110726-12_all +seqan-dev_1.3.1-1_all +sfftw-dev_2.1.5-1_i386 +skalibs-dev_0.47-1_i386 +slurm-drmaa-dev_1.0.4-3_i386 +slurm-llnl-basic-plugins-dev_2.3.4-2+b1_i386 +snappea-dev_3.0d3-22_i386 +spl-dev_1.0~pre6-3.1+b1_i386 +sra-toolkit-libs-dev_2.1.7a-1_i386 +ss-dev_2.0-1.42.5-1.1_i386 +stx-btree-dev_0.8.6-1_all +styx-dev_1.8.0-1.1_i386 +supercollider-dev_1:3.4.5-1wheezy1_i386 +svdrpservice-dev_0.0.4-14_all +sweep-dev_0.9.3-6_all +swish-e-dev_2.4.7-3_i386 +syfi-dev_1.0.0.dfsg-1_all +system-tools-backends-dev_2.10.2-1_all +systemtap-sdt-dev_1.7-1+deb7u1_i386 +tcl-dev_8.5.0-2.1_all +tcl-memchan-dev_2.3-2_i386 +tcl-trf-dev_2.1.4-dfsg1-1_i386 +tcl8.4-dev_8.4.19-5_i386 +tcl8.5-dev_8.5.11-2_i386 +tclcl-dev_1.20-6_all +tclx8.4-dev_8.4.0-3_i386 +tclxml-dev_3.3~svn11-2_i386 +tdom-dev_0.8.3~20080525-3+nmu2_i386 +tesseract-ocr-dev_3.02.01-6_all +tix-dev_8.4.3-4_i386 +tk-dev_8.5.0-2.1_all +tk8.4-dev_8.4.19-5_i386 +tk8.5-dev_8.5.11-2_i386 +tqsllib-dev_2.2-5_i386 +trafficserver-dev_3.0.5-1_i386 +tuxpaint-dev_1:0.9.21-1.1_all +unagi-dev_0.3.3-2_i386 +unixodbc-dev_2.2.14p2-5_i386 +uthash-dev_1.9.5-1_all +uuid-dev_2.20.1-5.3_i386 +vdr-dev_1.7.28-1_all +vflib3-dev_3.6.14.dfsg-3+b1_i386 +voms-dev_2.0.8-1_i386 +vstream-client-dev_1.2-6.1_i386 +wcslib-dev_4.13.4-1_i386 +weechat-dev_0.3.8-1+deb7u1_all +wireshark-dev_1.8.2-5wheezy10_i386 +witty-dev_3.2.1-2_all +wordnet-dev_1:3.0-29_i386 +wzdftpd-dev_0.8.3-6.2_i386 +x11proto-bigreqs-dev_1:1.1.2-1_all +x11proto-composite-dev_1:0.4.2-2_all +x11proto-core-dev_7.0.23-1_all +x11proto-damage-dev_1:1.2.1-2_all +x11proto-dmx-dev_1:2.3.1-2_all +x11proto-dri2-dev_2.6-2_all +x11proto-fixes-dev_1:5.0-2_all +x11proto-fonts-dev_2.1.2-1_all +x11proto-gl-dev_1.4.15-1_all +x11proto-input-dev_2.2-1_all +x11proto-kb-dev_1.0.6-2_all +x11proto-print-dev_1.0.5-2_all +x11proto-randr-dev_1.3.2-2_all +x11proto-record-dev_1.14.2-1_all +x11proto-render-dev_2:0.11.1-2_all +x11proto-resource-dev_1.2.0-3_all +x11proto-scrnsaver-dev_1.2.2-1_all +x11proto-video-dev_2.3.1-2_all +x11proto-xcmisc-dev_1.2.2-1_all +x11proto-xext-dev_7.2.1-1_all +x11proto-xf86bigfont-dev_1.2.0-3_all +x11proto-xf86dga-dev_2.1-3_all +x11proto-xf86dri-dev_2.1.1-2_all +x11proto-xf86vidmode-dev_2.3.1-2_all +x11proto-xinerama-dev_1.2.1-2_all +xaw3dg-dev_1.5+E-18.2_i386 +xbmc-eventclients-dev_2:11.0~git20120510.82388d5-1_all +xfce4-panel-dev_4.8.6-4_i386 +xfslibs-dev_3.1.7+b1_i386 +xmhtml1-dev_1.1.7-18_i386 +xmms2-dev_0.8+dfsg-4_all +xorg-dev_1:7.7+3~deb7u1_all +xotcl-dev_1.6.7-2_i386 +xpaint-dev_2.9.1.4-3+b2_i386 +xserver-xorg-dev_2:1.12.4-6+deb7u2_i386 +xserver-xorg-input-evdev-dev_1:2.7.0-1_all +xserver-xorg-input-joystick-dev_1:1.6.1-1_all +xserver-xorg-input-synaptics-dev_1.6.2-2_all +xtrans-dev_1.2.7-1_all +xulrunner-dev_24.4.0esr-1~deb7u2_i386 +xutils-dev_1:7.7~1_i386 +xviewg-dev_3.2p1.4-28.1_i386 +yate-dev_4.1.0-1~dfsg-3_i386 +yorick-dev_2.2.02+dfsg-6_i386 +zathura-dev_0.1.2-4_all +zlib1g-dev_1:1.2.7.dfsg-13_i386 +znc-dev_0.206-2_i386 +zsh-dev_4.3.17-1_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t09_repo/SearchRepo2Test_gold b/src/github.com/smira/aptly/system/t09_repo/SearchRepo2Test_gold new file mode 100644 index 00000000..0082b4bf --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/SearchRepo2Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: local repo with name repo-xx not found diff --git a/src/github.com/smira/aptly/system/t09_repo/SearchRepo3Test_gold b/src/github.com/smira/aptly/system/t09_repo/SearchRepo3Test_gold new file mode 100644 index 00000000..58f09611 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/SearchRepo3Test_gold @@ -0,0 +1 @@ +ERROR: unable to search: parsing failed: unexpected token : expecting ')' diff --git a/src/github.com/smira/aptly/system/t09_repo/SearchRepo4Test_gold b/src/github.com/smira/aptly/system/t09_repo/SearchRepo4Test_gold new file mode 100644 index 00000000..007f3b4f --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/SearchRepo4Test_gold @@ -0,0 +1,95 @@ + +coreutils_8.13-3.5_amd64 +coreutils_8.13-3.5_i386 +debconf_1.5.49_all +dpkg_1.16.12_amd64 +dpkg_1.16.12_i386 +fontconfig-config_2.9.0-7.1_all +fonts-freefont-ttf_20120503-1_all +gcc-4.7-base_4.7.2-5_amd64 +gcc-4.7-base_4.7.2-5_i386 +gsfonts-x11_0.22_all +gsfonts_1:8.11+urwcyr1.0.7~pre44-4.2_all +libacl1_2.2.51-8_amd64 +libacl1_2.2.51-8_i386 +libattr1_1:2.4.46-8_amd64 +libattr1_1:2.4.46-8_i386 +libbz2-1.0_1.0.6-4_amd64 +libbz2-1.0_1.0.6-4_i386 +libc-bin_2.13-38+deb7u1_amd64 +libc-bin_2.13-38+deb7u1_i386 +libc6_2.13-38+deb7u1_amd64 +libc6_2.13-38+deb7u1_i386 +libexpat1_2.1.0-1+deb7u1_amd64 +libexpat1_2.1.0-1+deb7u1_i386 +libfontconfig1_2.9.0-7.1_amd64 +libfontconfig1_2.9.0-7.1_i386 +libfontenc1_1:1.1.1-1_amd64 +libfontenc1_1:1.1.1-1_i386 +libfreetype6_2.4.9-1.1_amd64 +libfreetype6_2.4.9-1.1_i386 +libgcc1_1:4.7.2-5_amd64 +libgcc1_1:4.7.2-5_i386 +libgcrypt11_1.5.0-5+deb7u1_amd64 +libgcrypt11_1.5.0-5+deb7u1_i386 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-noxpm_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_amd64 +libgd2-xpm_2.0.36~rc1~dfsg-6.1_i386 +libgeoip1_1.4.8+dfsg-3_amd64 +libgeoip1_1.4.8+dfsg-3_i386 +libgpg-error0_1.10-3.1_amd64 +libgpg-error0_1.10-3.1_i386 +libjpeg8_8d-1_amd64 +libjpeg8_8d-1_i386 +liblzma5_5.1.1alpha+20120614-2_amd64 +liblzma5_5.1.1alpha+20120614-2_i386 +libpam0g_1.1.3-7.1_amd64 +libpam0g_1.1.3-7.1_i386 +libpcre3_1:8.30-5_amd64 +libpcre3_1:8.30-5_i386 +libpng12-0_1.2.49-1_amd64 +libpng12-0_1.2.49-1_i386 +libselinux1_2.1.9-5_amd64 +libselinux1_2.1.9-5_i386 +libssl1.0.0_1.0.1e-2+deb7u7_amd64 +libssl1.0.0_1.0.1e-2+deb7u7_i386 +libx11-6_2:1.5.0-1+deb7u1_amd64 +libx11-6_2:1.5.0-1+deb7u1_i386 +libx11-data_2:1.5.0-1+deb7u1_all +libxau6_1:1.0.7-1_amd64 +libxau6_1:1.0.7-1_i386 +libxcb1_1.8.1-2+deb7u1_amd64 +libxcb1_1.8.1-2+deb7u1_i386 +libxdmcp6_1:1.1.1-1_amd64 +libxdmcp6_1:1.1.1-1_i386 +libxfont1_1:1.4.5-3_amd64 +libxfont1_1:1.4.5-3_i386 +libxml2_2.8.0+dfsg1-7+nmu3_amd64 +libxml2_2.8.0+dfsg1-7+nmu3_i386 +libxpm4_1:3.5.10-1_amd64 +libxpm4_1:3.5.10-1_i386 +libxslt1.1_1.1.26-14.1_amd64 +libxslt1.1_1.1.26-14.1_i386 +lsb-base_4.1+Debian8+deb7u1_all +multiarch-support_2.13-38+deb7u1_amd64 +multiarch-support_2.13-38+deb7u1_i386 +nginx-common_1.2.1-2.2+wheezy2_all +nginx-full_1.2.1-2.2+wheezy2_amd64 +nginx-full_1.2.1-2.2+wheezy2_i386 +nginx-light_1.2.1-2.2+wheezy2_amd64 +nginx-light_1.2.1-2.2+wheezy2_i386 +nginx_1.2.1-2.2+wheezy2_all +perl-base_5.14.2-21+deb7u1_amd64 +perl-base_5.14.2-21+deb7u1_i386 +tar_1.26+dfsg-0.1_amd64 +tar_1.26+dfsg-0.1_i386 +ttf-bitstream-vera_1.10-8_all +ttf-dejavu-core_2.33-3_all +ucf_3.0025+nmu3_all +x11-common_1:7.7+3~deb7u1_all +xfonts-encodings_1:1.0.4-1_all +xfonts-utils_1:7.7~1_amd64 +xfonts-utils_1:7.7~1_i386 +zlib1g_1:1.2.7.dfsg-13_amd64 +zlib1g_1:1.2.7.dfsg-13_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t09_repo/__init__.py b/src/github.com/smira/aptly/system/t09_repo/__init__.py index 52760f06..54b70ef5 100644 --- a/src/github.com/smira/aptly/system/t09_repo/__init__.py +++ b/src/github.com/smira/aptly/system/t09_repo/__init__.py @@ -13,3 +13,4 @@ from .move import * from .remove import * from .show import * from .rename import * +from .search import * diff --git a/src/github.com/smira/aptly/system/t09_repo/add.py b/src/github.com/smira/aptly/system/t09_repo/add.py index 225326b1..9b5d9319 100644 --- a/src/github.com/smira/aptly/system/t09_repo/add.py +++ b/src/github.com/smira/aptly/system/t09_repo/add.py @@ -216,3 +216,54 @@ class AddRepo10Test(BaseTest): def check(self): self.check_output() self.check_cmd_output("aptly repo show -with-packages repo10", "repo_show") + + +class AddRepo11Test(BaseTest): + """ + add package to local repo: conflict in packages + -force-replace + """ + fixtureCmds = [ + "aptly repo create -comment=Repo11 -distribution=squeeze repo11", + "aptly repo add repo11 ${files}/pyspi_0.6.1-1.3.dsc", + ] + runCmd = "aptly repo add -force-replace repo11 ${testfiles}/pyspi_0.6.1-1.3.conflict.dsc" + outputMatchPrepare = lambda self, s: s.replace(os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.__class__.__name__), "").replace(os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files"), "") + + def check(self): + self.check_output() + self.check_cmd_output("aptly repo show -with-packages repo11", "repo_show") + + +class AddRepo12Test(BaseTest): + """ + add package to local repo: .udeb file + """ + fixtureCmds = [ + "aptly repo create -comment=Repo12 -distribution=squeeze repo12", + ] + runCmd = "aptly repo add repo12 ${udebs}/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb" + + def check(self): + self.check_output() + self.check_cmd_output("aptly repo show -with-packages repo12", "repo_show") + + # check pool + self.check_exists('pool/72/16/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb') + + +class AddRepo13Test(BaseTest): + """ + add package to local repo: .udeb and .deb files + """ + fixtureCmds = [ + "aptly repo create -comment=Repo13 -distribution=squeeze repo13", + ] + runCmd = "aptly repo add repo13 ${udebs} ${files}" + + def check(self): + self.check_output() + self.check_cmd_output("aptly repo show -with-packages repo13", "repo_show") + + # check pool + self.check_exists('pool/72/16/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb') + self.check_exists('pool/b7/2c/pyspi_0.6.1-1.3.dsc') diff --git a/src/github.com/smira/aptly/system/t09_repo/search.py b/src/github.com/smira/aptly/system/t09_repo/search.py new file mode 100644 index 00000000..01a89558 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/search.py @@ -0,0 +1,39 @@ +from lib import BaseTest + + +class SearchRepo1Test(BaseTest): + """ + search repo: regular search + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"] + runCmd = "aptly repo search wheezy-main '$$Architecture (i386), Name (% *-dev)'" + + +class SearchRepo2Test(BaseTest): + """ + search repo: missing repo + """ + runCmd = "aptly repo search repo-xx 'Name'" + expectedCode = 1 + + +class SearchRepo3Test(BaseTest): + """ + search repo: wrong expression + """ + fixtureDB = True + fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"] + expectedCode = 1 + runCmd = "aptly repo search wheezy-main '$$Architecture (i386'" + + +class SearchRepo4Test(BaseTest): + """ + search repo: with-deps search + """ + fixtureDB = True + fixtureCmds = ["aptly repo create wheezy-main", "aptly repo import wheezy-main wheezy-main Name"] + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly repo search -with-deps wheezy-main 'Name (nginx)'" diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold new file mode 100644 index 00000000..30358516 --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold @@ -0,0 +1,26 @@ +1) [Running]: repo list + +Begin command output: ---------------------------- +No local repositories found, create one with `aptly repo create ...`. + +End command output: ------------------------------ +2) [Running]: repo create local + +Begin command output: ---------------------------- + +Local repo [local] successfully added. +You can run 'aptly repo add local ...' to add packages to repository. + +End command output: ------------------------------ +3) [Running]: repo drop local + +Begin command output: ---------------------------- +Local repo `local` has been removed. + +End command output: ------------------------------ +4) [Running]: version + +Begin command output: ---------------------------- +aptly version: 0.8~dev + +End command output: ------------------------------ diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask2Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask2Test_gold new file mode 100644 index 00000000..bcdf84ba --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask2Test_gold @@ -0,0 +1,22 @@ +1) [Running]: repo list -raw + +Begin command output: ---------------------------- + +End command output: ------------------------------ +2) [Running]: repo create local + +Begin command output: ---------------------------- + +Local repo [local] successfully added. +You can run 'aptly repo add local ...' to add packages to repository. + +End command output: ------------------------------ +3) [Running]: repo list + +Begin command output: ---------------------------- +List of local repos: + * [local] (packages: 0) + +To get more information about local repository, run `aptly repo show `. + +End command output: ------------------------------ diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask3Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask3Test_gold new file mode 100644 index 00000000..6ca53cbb --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask3Test_gold @@ -0,0 +1,9 @@ +1) [Running]: repo show a + +Begin command output: ---------------------------- +ERROR: unable to show: local repo with name a not found + +End command output: ------------------------------ +2) [Skipping]: repo create local +3) [Skipping]: repo list +ERROR: at least one command has reported an error diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask4Test/task b/src/github.com/smira/aptly/system/t10_task/RunTask4Test/task new file mode 100644 index 00000000..5baee0af --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask4Test/task @@ -0,0 +1,3 @@ +repo list +repo create -distribution="Cool" abc +repo show abc diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask4Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask4Test_gold new file mode 100644 index 00000000..31067b4c --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask4Test_gold @@ -0,0 +1,26 @@ +Reading file... + +1) [Running]: repo list + +Begin command output: ---------------------------- +No local repositories found, create one with `aptly repo create ...`. + +End command output: ------------------------------ +2) [Running]: repo create -distribution=Cool abc + +Begin command output: ---------------------------- + +Local repo [abc] successfully added. +You can run 'aptly repo add abc ...' to add packages to repository. + +End command output: ------------------------------ +3) [Running]: repo show abc + +Begin command output: ---------------------------- +Name: abc +Comment: +Default Distribution: Cool +Default Component: main +Number of packages: 0 + +End command output: ------------------------------ diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask5Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask5Test_gold new file mode 100644 index 00000000..62a0741b --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/RunTask5Test_gold @@ -0,0 +1,2 @@ +ERROR: no such file, not_found + diff --git a/src/github.com/smira/aptly/system/t10_task/__init__.py b/src/github.com/smira/aptly/system/t10_task/__init__.py new file mode 100644 index 00000000..dd3ca362 --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/__init__.py @@ -0,0 +1,6 @@ +""" +Test aptly task run +""" + +# disabled for now +#from .run import * diff --git a/src/github.com/smira/aptly/system/t10_task/run.py b/src/github.com/smira/aptly/system/t10_task/run.py new file mode 100644 index 00000000..2d7897ec --- /dev/null +++ b/src/github.com/smira/aptly/system/t10_task/run.py @@ -0,0 +1,38 @@ +from lib import BaseTest + + +class RunTask1Test(BaseTest): + """ + task run: simple commands, 1-word command + """ + runCmd = "aptly task run repo list, repo create local, repo drop local, version" + + +class RunTask2Test(BaseTest): + """ + task run: commands with args + """ + runCmd = "aptly task run -- repo list -raw, repo create local, repo list" + + +class RunTask3Test(BaseTest): + """ + task run: failure + """ + expectedCode = 1 + runCmd = "aptly task run -- repo show a, repo create local, repo list" + + +class RunTask4Test(BaseTest): + """ + task run: from file + """ + runCmd = "aptly task run -filename=${testfiles}/task" + + +class RunTask5Test(BaseTest): + """ + task run: from file not found + """ + expectedCode = 1 + runCmd = "aptly task run -filename=not_found" diff --git a/src/github.com/smira/aptly/system/t11_package/SearchPackage1Test_gold b/src/github.com/smira/aptly/system/t11_package/SearchPackage1Test_gold new file mode 100644 index 00000000..e6017123 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/SearchPackage1Test_gold @@ -0,0 +1,4309 @@ + +amd-opencl-dev_1:12-6+point-3_i386 +amd-opencl-dev_1:13.12-4~bpo70+1_i386 +aolserver4-dev_4.5.1-15.1_i386 +apache2-prefork-dev_2.2.22-13+deb7u1_i386 +apache2-threaded-dev_2.2.22-13+deb7u1_i386 +apcalc-dev_2.12.4.4-3_i386 +aplus-fsf-dev_4.22.1-6_i386 +aroarfw-dev_0.1~beta4-5_all +asterisk-dev_1:1.8.13.1~dfsg1-3+deb7u3_all +asterisk-dev_1:11.10.2~dfsg-1~bpo70+1_all +atfs-dev_1.4pl6-11_i386 +audacious-dev_3.2.4-1_i386 +autotools-dev_20120608.1_all +binutils-dev_2.22-8_i386 +biosquid-dev_1.9g+cvs20050121-2_i386 +bitlbee-dev_3.0.5-1.2_all +blacs-pvm-dev_1.1-21_i386 +blends-dev_0.6.16.2_all +blktap-dev_2.0.90-1_i386 +blt-dev_2.4z-4.2_i386 +boinc-dev_7.0.27+dfsg-5_i386 +boinc-dev_7.2.47+dfsg-3~bpo70+1_all +boolstuff-dev_0.1.12-3_i386 +cairo-dock-dev_3.0.0-2+deb7u1_i386 +cernlib-base-dev_20061220+dfsg3-2_all +cernlib-core-dev_20061220+dfsg3-2_all +chipmunk-dev_5.3.4-1_i386 +cimg-dev_1.4.9-2_all +clearsilver-dev_0.10.5-1.3_i386 +cli-common-dev_0.8.2_all +clinica-dev_0.2.1~dfsg-1_i386 +clisp-dev_1:2.49-8.1_i386 +cluster-glue-dev_1.0.9+hg2665-1_i386 +codeblocks-dev_10.05-2.1_i386 +codeblocks-dev_13.12-1~bpo70+1_i386 +coinor-libcbc-dev_2.5.0-3_i386 +coinor-libcgl-dev_0.55.0-1.1_i386 +coinor-libclp-dev_1.12.0-2.1_i386 +coinor-libcoinutils-dev_2.6.4-3_i386 +coinor-libdylp-dev_1.6.0-1.1_i386 +coinor-libflopc++-dev_1.0.6-3.1_i386 +coinor-libipopt-dev_3.10.2-1.1_i386 +coinor-libosi-dev_0.103.0-1_i386 +coinor-libsymphony-dev_5.2.4-1.2_i386 +coinor-libvol-dev_1.1.7-1_i386 +collectd-dev_5.1.0-3_all +comerr-dev_2.1-1.42.5-1.1_i386 +condor-dev_7.8.2~dfsg.1-1+deb7u1_i386 +config-package-dev_4.13_all +config-package-dev_5.1.1~bpo70+1_all +connman-dev_1.0-1.1+wheezy1+b1_i386 +console-tools-dev_1:0.2.3dbs-70_i386 +coop-computing-tools-dev_3.5.1-2_i386 +corosync-dev_1.4.2-3_i386 +courier-authlib-dev_0.63.0-6+b1_i386 +crtmpserver-dev_1.0~dfsg-3_i386 +ctapi-dev_1.1_all +ctn-dev_3.0.6-13+b2_i386 +cyrus-dev_2.4.16-4+deb7u1_all +dcap-dev_2.47.6-2_i386 +dico-dev_2.1-3+b2_i386 +dictionaries-common-dev_1.12.11_all +dietlibc-dev_0.33~cvs20120325-4_i386 +dolfin-dev_1.0.0-7_all +dovecot-dev_1:2.1.7-7_i386 +dovecot-dev_1:2.2.9-1~bpo70+1_i386 +dpkg-dev_1.16.12_all +drac-dev_1.12-7.2_i386 +drizzle-plugin-dev_1:7.1.36-stable-1_i386 +dssi-dev_1.1.1~dfsg0-1_all +e2fslibs-dev_1.42.5-1.1_i386 +emerillon-dev_0.1.90-1_i386 +eog-dev_3.4.2-1+build1_all +eom-dev_1.8.0+dfsg1-2~bpo70+1_i386 +epiphany-browser-dev_3.4.2-2.1_i386 +erlang-dev_1:15.b.1-dfsg-4+deb7u1_i386 +erlang-dev_1:17.0-dfsg-3~bpo70+1_i386 +erlang-esdl-dev_1.2-2_all +etl-dev_0.04.15-1_i386 +evolution-data-server-dev_3.4.4-3_i386 +evolution-dev_3.4.4-3_i386 +exim4-dev_4.80-7_i386 +expect-dev_5.45-2_i386 +expeyes-firmware-dev_2.0.0-3_all +extremetuxracer-gimp-dev_0.4-5_all +falconpl-dev_0.9.6.9-git20120606-2_i386 +fatrat-dev_1.1.3-5_all +fcitx-libs-dev_1:4.2.4.1-7_i386 +fcitx-libs-dev_1:4.2.8.3-3~bpo70+1_i386 +fenix-dev_0.92a.dfsg1-9_all +festival-dev_1:2.1~release-5.1_i386 +fftw-dev_2.1.5-1_i386 +finch-dev_2.10.7-2~bpo70+1_all +finch-dev_2.10.9-1~deb7u1_all +firebird-dev_2.5.2.26540.ds4-1~deb7u1_i386 +flite1-dev_1.4-release-6_i386 +flow-tools-dev_1:0.68-12.1+b1_i386 +fosfat-dev_0.4.0-3_i386 +freecad-dev_0.13.2800-dfsg-1~bpo70+1_i386 +freeglut3-dev_2.6.0-4_i386 +freetds-dev_0.91-2+deb7u1_i386 +frei0r-plugins-dev_1.1.22git20091109-1.2_i386 +ftgl-dev_2.1.3~rc5-4_all +ftplib-dev_3.1-1-9_i386 +gambas3-dev_3.1.1-2+b1_i386 +gap-dev_4r4p12-2_i386 +gauche-dev_0.9.1-5.1_i386 +gcc-4.6-plugin-dev_4.6.3-14_i386 +gcc-4.7-plugin-dev_4.7.2-5_i386 +gcin-dev_2.7.6.1+dfsg-1_all +gedit-dev_3.4.2-1_all +gem-dev_1:0.93.3-5_all +genius-dev_1.0.14-1_i386 +gfxboot-dev_4.5.0-3_i386 +giblib-dev_1.2.4-8_i386 +glabels-dev_3.0.0-3+b1_i386 +glee-dev_5.4.0-1_i386 +gmpc-dev_11.8.16-6_i386 +gnash-dev_0.8.11~git20120629-1+deb7u1_i386 +gnash-dev_0.8.11~git20140319+dfsg-1~bpo70+1_i386 +gnome-control-center-dev_1:3.4.3.1-2_all +gnome-settings-daemon-dev_3.4.2+git20121218.7c1322-3+deb7u3_i386 +gnome-video-effects-dev_0.4.0-1_all +gnumach-dev_2:1.3.99.dfsg.git20120610-1_i386 +gnunet-dev_0.9.3-7_i386 +gnunet-gtk-dev_0.9.3-1_i386 +gnuradio-dev_3.5.3.2-1_i386 +gnuradio-dev_3.7.2.1-5~bpo70+1_i386 +gosa-dev_2.7.4-4.3~deb7u1_all +gpe-ownerinfo-dev_0.28-3_i386 +gpsim-dev_0.26.1-2.1_i386 +graphviz-dev_2.26.3-14+deb7u1_all +grass-dev_6.4.2-2_i386 +gridengine-drmaa-dev_6.2u5-7.1_i386 +gromacs-dev_4.5.5-2_i386 +gsettings-desktop-schemas-dev_3.4.2-3_i386 +gthumb-dev_3:3.0.1-2_i386 +guile-1.6-dev_1.6.8-10.3_i386 +guile-1.8-dev_1.8.8+1-8_i386 +guile-2.0-dev_2.0.5+1-3_i386 +guile-cairo-dev_1.4.0-3_i386 +heartbeat-dev_1:3.0.5-3_i386 +heimdal-dev_1.6~git20120403+dfsg1-2_i386 +hime-dev_0.9.9+git20120619+dfsg-1_all +hybrid-dev_1:7.2.2.dfsg.2-10_all +icedove-dev_10.0.12-1_i386 +inn2-dev_2.5.3-3_i386 +inventor-dev_2.1.5-10-16_i386 +iproute-dev_20120521-3+b3_i386 +iptables-dev_1.4.14-3.1_i386 +irssi-dev_0.8.15-5_i386 +isc-dhcp-dev_4.2.2.dfsg.1-5+deb70u6_i386 +itcl3-dev_3.4.1-1_i386 +itk3-dev_3.3-4_i386 +ivtools-dev_1.2.10a1-1_i386 +kadu-dev_0.11.2-1_all +kannel-dev_1.4.3-2+b2_i386 +kde-workspace-dev_4:4.8.4-6_i386 +kdebase-workspace-dev_4:4.8.4-6_all +kdelibs5-dev_4:4.8.4-4_i386 +kdemultimedia-dev_4:4.8.4-2_i386 +kdepimlibs5-dev_4:4.8.4-2_i386 +kdevelop-dev_4:4.3.1-3+b1_i386 +kdevplatform-dev_1.3.1-2_i386 +kmymoney-dev_4.6.2-3.2_i386 +konwert-dev_1.8-11.2_all +lam4-dev_7.1.4-3_i386 +lesstif2-dev_1:0.95.2-1.1_i386 +lib3ds-dev_1.3.0-6_i386 +lib4store-dev_1.1.4-2_i386 +lib64bz2-dev_1.0.6-4_i386 +lib64expat1-dev_2.1.0-1+deb7u1_i386 +lib64ffi-dev_3.0.10-3_i386 +lib64ncurses5-dev_5.9-10_i386 +lib64readline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +lib64readline6-dev_6.2+dfsg-0.1_i386 +lib64z1-dev_1:1.2.7.dfsg-13_i386 +liba52-0.7.4-dev_0.7.4-16_i386 +libaa1-dev_1.4p5-40_i386 +libaac-tactics-ocaml-dev_0.2.pl2-7_i386 +libaacs-dev_0.4.0-1_i386 +libaal-dev_1.0.5-5.1_i386 +libabiword-2.9-dev_2.9.2+svn20120603-8_i386 +libaccountsservice-dev_0.6.21-8_i386 +libace-dev_6.0.3+dfsg-0.1_i386 +libace-flreactor-dev_6.0.3+dfsg-0.1_i386 +libace-foxreactor-dev_6.0.3+dfsg-0.1_i386 +libace-htbp-dev_6.0.3+dfsg-0.1_i386 +libace-inet-dev_6.0.3+dfsg-0.1_i386 +libace-inet-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-qtreactor-dev_6.0.3+dfsg-0.1_i386 +libace-rmcast-dev_6.0.3+dfsg-0.1_i386 +libace-ssl-dev_6.0.3+dfsg-0.1_i386 +libace-tkreactor-dev_6.0.3+dfsg-0.1_i386 +libace-tmcast-dev_6.0.3+dfsg-0.1_i386 +libace-xtreactor-dev_6.0.3+dfsg-0.1_i386 +libacexml-dev_6.0.3+dfsg-0.1_i386 +libacl1-dev_2.2.51-8_i386 +libacpi-dev_0.2-4_i386 +libacr38ucontrol-dev_1.7.11-1_i386 +libadasockets4-dev_1.8.10-2_i386 +libaddresses-dev_0.4.7-1+b5_i386 +libaddressview-dev_0.4.7-1+b5_i386 +libadios-dev_1.3-11_i386 +libadminutil-dev_1.1.15-1_i386 +libadns1-dev_1.4-2_i386 +libadolc-dev_2.3.0-1_i386 +libadplug-dev_2.2.1+dfsg3-0.1_i386 +libafflib-dev_3.6.6-1.1+b1_i386 +libafrodite-0.12-dev_0.12.1-3_i386 +libafterimage-dev_2.2.11-7_i386 +libagg-dev_2.5+dfsg1-8_i386 +libagrep-ocaml-dev_1.0-11+b3_i386 +libahven3-dev_2.1-4_i386 +libaiksaurus-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaiksaurusgtk-1.2-dev_1.2.1+dev-0.12-6.1_i386 +libaio-dev_0.3.109-3_i386 +libakonadi-dev_1.7.2-3_i386 +libalberta2-dev_2.0.1-5_i386 +libaldmb1-dev_1:0.9.3-5.4_i386 +libalglib-dev_2.6.0-6_i386 +libalkimia-dev_4.3.2-1.1_i386 +liballeggl4-dev_2:4.4.2-2.1_i386 +liballegro4.2-dev_2:4.4.2-2.1_i386 +libalog0.4.1-base-dev_0.4.1-2_i386 +libalog0.4.1-full-dev_0.4.1-2_i386 +libalsa-ocaml-dev_0.2.1-1+b1_i386 +libalsaplayer-dev_0.99.80-5.1_i386 +libalure-dev_1.2-6_i386 +libalut-dev_1.1.0-3_i386 +libampsharp-cil-dev_2.0.4-2_all +libamu-dev_6.2+rc20110530-3_i386 +libanalitza-dev_4:4.8.4-2_i386 +libanet0.1-dev_0.1-3_i386 +libanjuta-dev_2:3.4.3-1_i386 +libann-dev_1.1.2+doc-3_i386 +libanthy-dev_9100h-16_i386 +libantlr-dev_2.7.7+dfsg-4_i386 +libantlr3c-dev_3.2-2_i386 +libao-dev_1.1.0-2_i386 +libao-ocaml-dev_0.2.0-1+b2_i386 +libaosd-dev_0.2.7-1_i386 +libapache2-mod-perl2-dev_2.0.7-3_all +libapertium3-3.1-0-dev_3.1.0-2_i386 +libapm-dev_3.2.2-14_i386 +libapol-dev_3.3.7-3_i386 +libapparmor-dev_2.7.103-4_i386 +libappindicator-dev_0.4.92-2_i386 +libappindicator0.1-cil-dev_0.4.92-2_all +libappindicator3-dev_0.4.92-2_i386 +libapq-postgresql3.2.0-dev_3.2.0-2_i386 +libapq3.2.0-dev_3.2.0-1_i386 +libapr-memcache-dev_0.7.0-1_i386 +libapr1-dev_1.4.6-3+deb7u1_i386 +libapreq2-dev_2.13-1+b2_i386 +libapron-dev_0.9.10-5.2_all +libapron-ocaml-dev_0.9.10-5.2+b3_i386 +libaprutil1-dev_1.4.1-3_i386 +libapt-pkg-dev_0.9.7.9+deb7u1_i386 +libaqbanking34-dev_5.0.24-3_i386 +libaqbanking34-dev_5.4.3beta-1~bpo70+1_i386 +libaqsis-dev_1.8.1-3_i386 +libarchive-dev_3.0.4-3+nmu1_i386 +libarchive-dev_3.1.2-8~bpo70+1_i386 +libargtable2-dev_12-1_i386 +libarmadillo-dev_1:3.2.3+dfsg-1_i386 +libarmadillo-dev_1:4.200.0+dfsg-1~bpo70+1_i386 +libarpack++2-dev_2.3-2_i386 +libarpack2-dev_3.1.1-2.1_i386 +libart-2.0-dev_2.3.21-2_i386 +libart2.0-cil-dev_2.24.2-3_all +libasio-dev_1.4.1-3.2_all +libasis2010-dev_2010-5_i386 +libasm-dev_0.152-1+wheezy1_i386 +libasound2-dev_1.0.25-4_i386 +libaspell-dev_0.60.7~20110707-1_i386 +libasprintf-dev_0.18.3-1~bpo7+1_i386 +libass-dev_0.10.0-3_i386 +libassa3.5-5-dev_3.5.1-2_i386 +libassimp-dev_3.0~dfsg-1_i386 +libassuan-dev_2.0.3-1_i386 +libast2-dev_0.7-6+b1_i386 +libasyncns-dev_0.8-4_i386 +libatasmart-dev_0.19-1_i386 +libatd-ocaml-dev_1.0.1-1+b1_i386 +libatdgen-ocaml-dev_1.2.2-1+b1_i386 +libatk-bridge2.0-dev_2.5.3-2_i386 +libatk1.0-dev_2.4.0-2_i386 +libatkmm-1.6-dev_2.22.6-1_i386 +libatlas-base-dev_3.8.4-9+deb7u1_i386 +libatlas-cpp-0.6-dev_0.6.2-3_i386 +libatlas-dev_3.8.4-9+deb7u1_all +libatm1-dev_1:2.5.1-1.5_i386 +libatomic-ops-dev_7.2~alpha5+cvs20101124-1+deb7u1_i386 +libatomicparsley-dev_2.1.2-1_i386 +libatrildocument-dev_1.8.0+dfsg1-2~bpo70+1_i386 +libatrilview-dev_1.8.0+dfsg1-2~bpo70+1_i386 +libatspi-dev_1.32.0-2_i386 +libatspi2.0-dev_2.5.3-2_i386 +libattica-dev_0.2.0-1_i386 +libattr1-dev_1:2.4.46-8_i386 +libaubio-dev_0.3.2-4.2+b1_i386 +libaudio-dev_1.9.3-5wheezy1_i386 +libaudiofile-dev_0.3.4-2_i386 +libaudiomask-dev_1.0-2_i386 +libaudit-dev_1:1.7.18-1.1_i386 +libaugeas-dev_0.10.0-1_i386 +libaunit2-dev_1.03-7_i386 +libautotrace-dev_0.31.1-16+b1_i386 +libautounit-dev_0.20.1-4_i386 +libavahi-cil-dev_0.6.19-4.2_all +libavahi-client-dev_0.6.31-2_i386 +libavahi-common-dev_0.6.31-2_i386 +libavahi-compat-libdnssd-dev_0.6.31-2_i386 +libavahi-core-dev_0.6.31-2_i386 +libavahi-glib-dev_0.6.31-2_i386 +libavahi-gobject-dev_0.6.31-2_i386 +libavahi-qt4-dev_0.6.31-2_i386 +libavahi-ui-cil-dev_0.6.19-4.2_all +libavahi-ui-dev_0.6.31-2_i386 +libavahi-ui-gtk3-dev_0.6.31-2_i386 +libavbin-dev_7-1.3_i386 +libavc1394-dev_0.5.4-2_i386 +libavcodec-dev_6:0.8.10-1_i386 +libavcodec-dev_6:10.1-1~bpo70+1_i386 +libavdevice-dev_6:0.8.10-1_i386 +libavdevice-dev_6:10.1-1~bpo70+1_i386 +libavfilter-dev_6:0.8.10-1_i386 +libavfilter-dev_6:10.1-1~bpo70+1_i386 +libavformat-dev_6:0.8.10-1_i386 +libavformat-dev_6:10.1-1~bpo70+1_i386 +libavifile-0.7-dev_1:0.7.48~20090503.ds-13_i386 +libavl-dev_0.3.5-3_i386 +libavogadro-dev_1.0.3-5_i386 +libavresample-dev_6:10.1-1~bpo70+1_i386 +libavutil-dev_6:0.8.10-1_i386 +libavutil-dev_6:10.1-1~bpo70+1_i386 +libaws2.10.2-dev_2.10.2-4_i386 +libax25-dev_0.0.12-rc2+cvs20120204-2_i386 +libbabl-dev_0.1.10-1_i386 +libball1.4-dev_1.4.1+20111206-4_i386 +libballview1.4-dev_1.4.1+20111206-4_i386 +libbam-dev_0.1.18-1_i386 +libbam-dev_0.1.19-1~bpo70+1_i386 +libbamf-dev_0.2.118-1_i386 +libbamf3-dev_0.2.118-1_i386 +libbarry-dev_0.18.3-5_i386 +libbatteries-ocaml-dev_1.4.3-1_i386 +libbdd-dev_2.4-8_i386 +libbeecrypt-dev_4.2.1-4_i386 +libbenchmark-ocaml-dev_0.9-2+b3_i386 +libbfb0-dev_0.23-1.1_i386 +libbfio-dev_20130507-1~bpo70+1_i386 +libbg1-dev_1.106-1_i386 +libbibutils-dev_4.12-5_i386 +libbin-prot-camlp4-dev_2.0.7-1_i386 +libbind-dev_1:9.8.4.dfsg.P1-6+nmu2+deb7u1_i386 +libbind-dev_1:9.9.5.dfsg-4~bpo70+1_i386 +libbind4-dev_6.0-1_i386 +libbinio-dev_1.4+dfsg1-1_i386 +libbiniou-ocaml-dev_1.0.0-1+b1_i386 +libbio2jack0-dev_0.9-2.1_i386 +libbiococoa-dev_2.2.2-1+b2_i386 +libbiosig-dev_1.3.0-2_i386 +libbisho-common-dev_0.27.2+git20111122.9e68ef3d-1_i386 +libbison-dev_1:2.5.dfsg-2.1_i386 +libbitmask-dev_2.0-2_i386 +libbitstream-dev_1.0-1_all +libbitstring-ocaml-dev_2.0.2-3+b1_i386 +libbjack-ocaml-dev_0.1.3-5+b1_i386 +libblacs-mpi-dev_1.1-31_i386 +libbladerf-dev_0.10.7.47.ebe70c4-4~bpo70+1_i386 +libblas-dev_1.2.20110419-5_i386 +libbliss-dev_0.72-4_i386 +libblitz0-dev_1:0.9-13_i386 +libblkid-dev_2.20.1-5.3_i386 +libblocksruntime-dev_0.1-1_i386 +libbluedevil-dev_1.9.2-1_i386 +libbluetooth-dev_4.99-2_i386 +libbluray-dev_1:0.2.2-1_i386 +libbml-dev_0.6.1-1_i386 +libbobcat-dev_3.01.00-1+b1_i386 +libbogl-dev_0.1.18-8+b1_i386 +libbognor-regis-dev_0.6.12+git20101007.02c25268-7_i386 +libboinc-app-dev_7.2.47+dfsg-3~bpo70+1_all +libbonobo2-dev_2.24.3-1_i386 +libbonoboui2-dev_2.24.3-1_i386 +libboo-cil-dev_0.9.5~git20110729.r1.202a430-2_all +libboost-all-dev_1.49.0.1_i386 +libboost-chrono-dev_1.49.0.1_i386 +libboost-chrono1.49-dev_1.49.0-3.2_i386 +libboost-date-time-dev_1.49.0.1_i386 +libboost-date-time1.49-dev_1.49.0-3.2_i386 +libboost-dev_1.49.0.1_i386 +libboost-filesystem-dev_1.49.0.1_i386 +libboost-filesystem1.49-dev_1.49.0-3.2_i386 +libboost-graph-dev_1.49.0.1_i386 +libboost-graph-parallel-dev_1.49.0.1_i386 +libboost-graph-parallel1.49-dev_1.49.0-3.2_i386 +libboost-graph1.49-dev_1.49.0-3.2_i386 +libboost-iostreams-dev_1.49.0.1_i386 +libboost-iostreams1.49-dev_1.49.0-3.2_i386 +libboost-locale-dev_1.49.0.1_i386 +libboost-locale1.49-dev_1.49.0-3.2_i386 +libboost-math-dev_1.49.0.1_i386 +libboost-math1.49-dev_1.49.0-3.2_i386 +libboost-mpi-dev_1.49.0.1_i386 +libboost-mpi-python-dev_1.49.0.1_i386 +libboost-mpi-python1.49-dev_1.49.0-3.2_i386 +libboost-mpi1.49-dev_1.49.0-3.2_i386 +libboost-program-options-dev_1.49.0.1_i386 +libboost-program-options1.49-dev_1.49.0-3.2_i386 +libboost-python-dev_1.49.0.1_i386 +libboost-python1.49-dev_1.49.0-3.2_i386 +libboost-random-dev_1.49.0.1_i386 +libboost-random1.49-dev_1.49.0-3.2_i386 +libboost-regex-dev_1.49.0.1_i386 +libboost-regex1.49-dev_1.49.0-3.2_i386 +libboost-serialization-dev_1.49.0.1_i386 +libboost-serialization1.49-dev_1.49.0-3.2_i386 +libboost-signals-dev_1.49.0.1_i386 +libboost-signals1.49-dev_1.49.0-3.2_i386 +libboost-system-dev_1.49.0.1_i386 +libboost-system1.49-dev_1.49.0-3.2_i386 +libboost-test-dev_1.49.0.1_i386 +libboost-test1.49-dev_1.49.0-3.2_i386 +libboost-thread-dev_1.49.0.1_i386 +libboost-thread1.49-dev_1.49.0-3.2_i386 +libboost-timer-dev_1.49.0.1_i386 +libboost-timer1.49-dev_1.49.0-3.2_i386 +libboost-wave-dev_1.49.0.1_i386 +libboost-wave1.49-dev_1.49.0-3.2_i386 +libboost1.49-all-dev_1.49.0-3.2_i386 +libboost1.49-dev_1.49.0-3.2_i386 +libbotan1.10-dev_1.10.5-1_i386 +libbox-dev_2.5-2_i386 +libbox2d-dev_2.0.1+dfsg1-1_i386 +libbpp-core-dev_2.0.3-1_i386 +libbpp-phyl-dev_2.0.3-1_i386 +libbpp-popgen-dev_2.0.3-1_i386 +libbpp-qt-dev_2.0.2-1_i386 +libbpp-raa-dev_2.0.3-1_i386 +libbpp-seq-dev_2.0.3-1_i386 +libbrahe-dev_1.3.2-3_i386 +libbrasero-media3-dev_3.4.1-4_i386 +libbrlapi-dev_4.4-10+deb7u1_i386 +libbs2b-dev_3.1.0+dfsg-2_i386 +libbsd-dev_0.4.2-1_i386 +libbse-dev_0.7.4-5_all +libbt-dev_0.70.1-13_i386 +libbtparse-dev_0.63-1_i386 +libbuffy-dev_1.7-1_i386 +libbullet-dev_2.82-r2704+dfsg-2~bpo70+1_i386 +libbullet-extras-dev_2.82-r2704+dfsg-2~bpo70+1_i386 +libbulletml-dev_0.0.6-5_i386 +libburn-dev_1.2.2-2_i386 +libbuzztard-dev_0.5.0-4_i386 +libbz2-dev_1.0.6-4_i386 +libbz2-ocaml-dev_0.6.0-6+b2_i386 +libc-ares-dev_1.9.1-3_i386 +libc-client2007e-dev_8:2007f~dfsg-2_i386 +libc6-dev_2.13-38+deb7u1_i386 +libcableswig-dev_0.1.0+cvs20111009-1_i386 +libcaca-dev_0.99.beta18-1_i386 +libcairo-ocaml-dev_1:1.2.0-2+b1_i386 +libcairo2-dev_1.12.2-3_i386 +libcairomm-1.0-dev_1.10.0-1_i386 +libcaja-extension-dev_1.8.1-2~bpo70+1_i386 +libcajun-dev_2.0.3-1~bpo70+1_all +libcal3d12-dev_0.11.0-4.1_i386 +libcalendar-ocaml-dev_2.03-1+b2_i386 +libcamel1.2-dev_3.4.4-3_i386 +libcameleon-ocaml-dev_1.9.21-2+b1_i386 +libcaml2html-ocaml-dev_1.4.1-3_i386 +libcamlimages-ocaml-dev_1:4.0.1-4+b2_i386 +libcamljava-ocaml-dev_0.3-1+b3_i386 +libcamlpdf-ocaml-dev_0.5-1+b2_i386 +libcamltemplate-ocaml-dev_1.0.2-1+b2_i386 +libcamomile-ocaml-dev_0.8.4-2_i386 +libcanberra-dev_0.28-6_i386 +libcanberra-gtk-common-dev_0.28-6_all +libcanberra-gtk-dev_0.28-6_i386 +libcanberra-gtk3-dev_0.28-6_i386 +libcanlock2-dev_2b-6_i386 +libcanna1g-dev_3.7p3-11_i386 +libcap-dev_1:2.22-1.2_i386 +libcap-ng-dev_0.6.6-2_i386 +libcapi20-dev_1:3.25+dfsg1-3.3~deb7u1_i386 +libcapsinetwork-dev_0.3.0-7_i386 +libcaribou-dev_0.4.4-1_i386 +libcbf-dev_0.7.9.1-3_i386 +libccaudio2-dev_2.0.5-3_i386 +libccfits-dev_2.4-1_i386 +libcconv-dev_0.6.2-1_i386 +libccrtp-dev_2.0.3-4_i386 +libccs-dev_3.0.12-3.2+deb7u2_i386 +libccscript3-dev_1.1.7-2_i386 +libccss-dev_0.5.0-4_i386 +libcdaudio-dev_0.99.12p2-12_i386 +libcdb-dev_0.78_i386 +libcdd-dev_094b.dfsg-4.2_i386 +libcddb2-dev_1.3.2-3_i386 +libcdi-dev_1.5.4+dfsg.1-5_i386 +libcdio-cdda-dev_0.83-4_i386 +libcdio-dev_0.83-4_i386 +libcdio-paranoia-dev_0.83-4_i386 +libcdk5-dev_5.0.20060507-4_i386 +libcdparanoia-dev_3.10.2+debian-10.1_i386 +libcec-dev_1.6.2-1.1_i386 +libcec-dev_2.1.4-1~bpo70+1_i386 +libcegui-mk2-dev_0.7.6-2+b1_i386 +libcephfs-dev_0.80.1-1~bpo70+1_i386 +libcext-dev_6.1.1-2_i386 +libcf-ocaml-dev_0.10-3+b3_i386 +libcfg-dev_1.4.2-3_i386 +libcfitsio3-dev_3.300-2_i386 +libcgal-dev_4.0-5_i386 +libcgic-dev_2.05-3_i386 +libcgicc5-dev_3.2.9-3_i386 +libcgns-dev_3.1.3.4-1+b1_i386 +libcgroup-dev_0.38-1_i386 +libcgsi-gsoap-dev_1.3.5-1_i386 +libchamplain-0.12-dev_0.12.3-1_i386 +libchamplain-gtk-0.12-dev_0.12.3-1_i386 +libcharls-dev_1.0-2_i386 +libchasen-dev_2.4.5-6_i386 +libcheese-dev_3.4.2-2_i386 +libcheese-gtk-dev_3.4.2-2_i386 +libchewing3-dev_0.3.3-4_i386 +libchicken-dev_4.7.0-1_i386 +libchipcard-dev_5.0.3beta-3_i386 +libchise-dev_0.3.0-2+b1_i386 +libchm-dev_2:0.40a-2_i386 +libchromaprint-dev_0.6-2_i386 +libchromaprint-dev_1.1-1~bpo70+1_i386 +libcib1-dev_1.1.7-1_i386 +libcitadel-dev_8.14-1_i386 +libcitygml0-dev_0.14+svn128-1+3p0p1+4_i386 +libck-connector-dev_0.4.5-3.1_i386 +libck-connector-dev_0.4.6-4~bpo70+1_i386 +libckyapplet1-dev_1.1.0-12_i386 +libclalsadrv-dev_2.0.0-3_all +libclam-dev_1.4.0-5.1_i386 +libclam-qtmonitors-dev_1.4.0-3.1_i386 +libclamav-dev_0.98.1+dfsg-1+deb7u3_i386 +libclamav-dev_0.98.1+dfsg-1+deb7u4_i386 +libclang-common-dev_1:3.0-6.2_i386 +libclang-dev_1:3.0-6.2_i386 +libclanlib-dev_1.0~svn3827-3_i386 +libclassad-dev_7.8.2~dfsg.1-1+deb7u1_i386 +libclaw-application-dev_1.7.0-3_i386 +libclaw-configuration-file-dev_1.7.0-3_i386 +libclaw-dev_1.7.0-3_i386 +libclaw-dynamic-library-dev_1.7.0-3_i386 +libclaw-graphic-dev_1.7.0-3_i386 +libclaw-logger-dev_1.7.0-3_i386 +libclaw-net-dev_1.7.0-3_i386 +libclaw-tween-dev_1.7.0-3_i386 +libclaws-mail-dev_3.8.1-2_i386 +libclaws-mail-dev_3.9.3-1~bpo70+1_i386 +libclhep-dev_2.1.2.3-1_i386 +libcli-dev_1.9.6-1_i386 +libclippoly-dev_0.11-3_i386 +libclips-dev_6.24-3_i386 +libcliquer-dev_1.21-1_i386 +libcln-dev_1.3.2-1.2_i386 +libcloog-isl-dev_0.17.0-3_i386 +libcloog-ppl-dev_0.15.11-4_i386 +libclthreads-dev_2.4.0-4_i386 +libclucene-dev_0.9.21b-2+b1_i386 +libclustalo-dev_1.1.0-1_i386 +libcluster-glue-dev_1.0.9+hg2665-1_all +libclutter-1.0-dev_1.10.8-2_i386 +libclutter-cil-dev_1.0.0~alpha3~git20090817.r1.349dba6-8_all +libclutter-gst-dev_1.5.4-1+build0_i386 +libclutter-gtk-1.0-dev_1.2.0-2_i386 +libclutter-imcontext-0.1-dev_0.1.4-3_i386 +libcluttergesture-dev_0.0.2.1-7_i386 +libclxclient-dev_3.6.1-6_i386 +libcman-dev_3.0.12-3.2+deb7u2_i386 +libcminpack-dev_1.2.2-1_i386 +libcmis-dev_0.1.0-1+b1_i386 +libcmor-dev_2.8.0-2+b1_i386 +libcmph-dev_0.9-1_i386 +libcneartree-dev_3.1.1-1_i386 +libcnf-dev_4.0-2_i386 +libcob1-dev_1.1-1_i386 +libcogl-dev_1.10.2-7_i386 +libcogl-pango-dev_1.10.2-7_i386 +libcoin60-dev_3.1.3-2.2_i386 +libcoin80-dev_3.1.4~abc9f50-3~bpo70+1_i386 +libcojets2-dev_20061220+dfsg3-2_i386 +libcollectdclient-dev_5.1.0-3_i386 +libcollection-dev_0.1.3-2_i386 +libcolorblind-dev_0.0.1-1_i386 +libcolord-dev_0.1.21-1_i386 +libcolord-gtk-dev_0.1.21-1_i386 +libcolorhug-dev_0.1.10-1_i386 +libcomedi-dev_0.10.0-3_i386 +libcommoncpp2-dev_1.8.1-5_i386 +libcompfaceg1-dev_1:1.5.2-5_i386 +libconcord-dev_0.24-1.1_i386 +libconfdb-dev_1.4.2-3_i386 +libconfig++-dev_1.4.8-5_i386 +libconfig++8-dev_1.4.8-5_i386 +libconfig-dev_1.4.8-5_i386 +libconfig-file-ocaml-dev_1.1-1_i386 +libconfig8-dev_1.4.8-5_i386 +libconfuse-dev_2.7-4_i386 +libcontactsdb-dev_0.5-8_i386 +libcoq-ocaml-dev_8.3.pl4+dfsg-2_i386 +libcore++-dev_1.7-12_i386 +libcore-ocaml-dev_107.01-5_i386 +libcorelinux-dev_0.4.32-7.3_i386 +libcoroipcc-dev_1.4.2-3_i386 +libcoroipcs-dev_1.4.2-3_i386 +libcorosync-dev_1.4.2-3_all +libcos4-dev_4.1.6-2_i386 +libcothreads-ocaml-dev_0.10-3+b3_i386 +libcoyotl-dev_3.1.0-5_i386 +libcpg-dev_1.4.2-3_i386 +libcpl-dev_6.1.1-2_i386 +libcppcutter-dev_1.1.7-1.2_i386 +libcppunit-dev_1.12.1-4_i386 +libcppunit-subunit-dev_0.0.8+bzr176-1_i386 +libcpputest-dev_3.1-2_i386 +libcpufreq-dev_008-1_i386 +libcpuset-dev_1.0-3_i386 +libcqrlib2-dev_1.1.2-1_i386 +libcr-dev_0.8.5-2_i386 +libcrack2-dev_2.8.19-3_i386 +libcreal-ocaml-dev_0.7-6+b3_i386 +libcrmcluster1-dev_1.1.7-1_i386 +libcrmcommon2-dev_1.1.7-1_i386 +libcroco3-dev_0.6.6-2_i386 +libcry-ocaml-dev_0.2.2-1+b1_i386 +libcryptgps-ocaml-dev_0.2.1-7+b3_i386 +libcrypto++-dev_5.6.1-6_i386 +libcryptokit-ocaml-dev_1.5-1_i386 +libcryptsetup-dev_2:1.4.3-4_i386 +libcryptui-dev_3.2.2-1_i386 +libcrystalhd-dev_1:0.0~git20110715.fdd2f19-9_i386 +libcsfml-dev_1.6-1_i386 +libcsnd-dev_1:5.17.11~dfsg-3_all +libcsoap-dev_1.1.0-17.1_i386 +libcsound64-dev_1:5.17.11~dfsg-3_all +libcsoundac-dev_1:5.17.11~dfsg-3_all +libcsv-ocaml-dev_1.2.2-1+b1_i386 +libcsvimp-dev_0.4.7-2~bpo70+1_i386 +libctapimkt0-dev_1.0.1-1.1_i386 +libctdb-dev_1.12+git20120201-4_i386 +libctdb-dev_2.5.3+debian0-1~bpo70+1_i386 +libctemplate-dev_2.2-3_i386 +libctl-dev_3.1.0-5_i386 +libctpl-dev_0.3.3.dfsg-2_i386 +libcuba3-dev_3.0+20111124-2_i386 +libcudf-dev_0.6.2-1_i386 +libcudf-ocaml-dev_0.6.2-1_i386 +libcue-dev_1.4.0-1_i386 +libcuneiform-dev_1.1.0+dfsg-4_i386 +libcunit1-dev_2.1-0.dfsg-10_i386 +libcunit1-ncurses-dev_2.1-0.dfsg-10_i386 +libcups2-dev_1.5.3-5+deb7u1_i386 +libcupscgi1-dev_1.5.3-5+deb7u1_i386 +libcupsdriver1-dev_1.5.3-5+deb7u1_i386 +libcupsfilters-dev_1.0.18-2.1+deb7u1_i386 +libcupsimage2-dev_1.5.3-5+deb7u1_i386 +libcupsmime1-dev_1.5.3-5+deb7u1_i386 +libcupsppdc1-dev_1.5.3-5+deb7u1_i386 +libcupt2-dev_2.5.9_i386 +libcupti-dev_4.2.9-2_i386 +libcupti-dev_5.0.35-8~bpo70+1_i386 +libcurl-ocaml-dev_0.5.3-2+b1_i386 +libcurl4-gnutls-dev_7.26.0-1+wheezy9_i386 +libcurl4-nss-dev_7.26.0-1+wheezy9_i386 +libcurl4-openssl-dev_7.26.0-1+wheezy9_i386 +libcurses-ocaml-dev_1.0.3-2_i386 +libcutter-dev_1.1.7-1.2_i386 +libcv-dev_2.3.1-11_i386 +libcvaux-dev_2.3.1-11_i386 +libcvc3-dev_2.4.1-4_i386 +libcvector2-dev_1.0.3-1_i386 +libcvm1-dev_0.96-1+b1_i386 +libcw3-dev_3.0.2-1_i386 +libcwidget-dev_0.5.16-3.4_i386 +libcwiid-dev_0.6.00+svn201-3+b1_i386 +libcwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libcxgb3-dev_1.3.1-1_i386 +libcxxtools-dev_2.1.1-1_i386 +libdacs-dev_1.4.27b-2_i386 +libdaemon-dev_0.14-2_i386 +libdancer-xml0-dev_0.8.2.1-3_i386 +libdap-dev_3.11.1-11_i386 +libdapl-dev_2.0.19-1.1_i386 +libdaq-dev_0.6.2-2_i386 +libdar-dev_2.4.5.debian.1-1_i386 +libdatrie-dev_0.2.5-3_i386 +libdawgdic-dev_0.4.3-1_all +libdb++-dev_5.1.6_i386 +libdb-dev_5.1.6_i386 +libdb-java-dev_5.1.6_i386 +libdb-sql-dev_5.1.6_i386 +libdb4o-cil-dev_8.0.184.15484+dfsg-2_all +libdb5.1++-dev_5.1.29-5_i386 +libdb5.1-dev_5.1.29-5_i386 +libdb5.1-java-dev_5.1.29-5_i386 +libdb5.1-sql-dev_5.1.29-5_i386 +libdb5.1-stl-dev_5.1.29-5_i386 +libdballe-dev_5.18-1_i386 +libdballef-dev_5.18-1_i386 +libdbaudiolib0-dev_0.9.8-6.2_i386 +libdbi-dev_0.8.4-6_i386 +libdbus-1-dev_1.6.8-1+deb7u1_i386 +libdbus-c++-dev_0.9.0-6_i386 +libdbus-glib-1-dev_0.100.2-1_i386 +libdbus-glib1.0-cil-dev_0.5.0-4_all +libdbus-ocaml-dev_0.29-1+b3_i386 +libdbus1.0-cil-dev_0.7.0-5_all +libdbusada0.2-dev_0.2-2_i386 +libdbusmenu-glib-dev_0.6.2-1_i386 +libdbusmenu-gtk-dev_0.6.2-1_i386 +libdbusmenu-gtk3-dev_0.6.2-1_i386 +libdbusmenu-jsonloader-dev_0.6.2-1_i386 +libdbusmenu-qt-dev_0.9.0-1_i386 +libdc-dev_0.3.24~svn3121-2_i386 +libdc1394-22-dev_2.2.0-2_i386 +libdca-dev_0.0.5-5_i386 +libdcerpc-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcerpc-server-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libdcmtk2-dev_3.6.0-12_i386 +libdconf-dbus-1-dev_0.12.1-3_i386 +libdconf-dev_0.12.1-3_i386 +libddccontrol-dev_0.4.2-10_i386 +libdds-dev_2.1.2+ddd105-1_i386 +libdebconf-kde-dev_0.2-2_i386 +libdebconfclient0-dev_0.182_i386 +libdebian-installer4-dev_0.87_i386 +libdebug0-dev_0.4.4-1.1_i386 +libdecodeqr-dev_0.9.3-6.2_i386 +libdee-dev_1.0.10-3_i386 +libderiving-ocaml-dev_0.1.1a-3+b1_i386 +libderiving-ocsigen-ocaml-dev_0.3c-1_i386 +libdesktop-agnostic-dev_0.3.92+dfsg-1_i386 +libdessert0.87-dev_0.87.2-1_i386 +libdevhelp-dev_3.4.1-1_i386 +libdevil-dev_1.7.8-6.1+b1_i386 +libdevmapper-dev_2:1.02.74-8_i386 +libdhash-dev_0.1.3-2_i386 +libdiagnostics-dev_0.3.3-1.3_i386 +libdianewcanvas2-dev_0.6.10-5.4_i386 +libdieharder-dev_3.31.1-4_i386 +libdiet-admin2.8-dev_2.8.0-1+b1_i386 +libdiet-client2.8-dev_2.8.0-1+b1_i386 +libdiet-dagda2.8-dev_2.8.0-1+b1_i386 +libdiet-sed2.8-dev_2.8.0-1+b1_i386 +libdime-dev_0.20030921-2_all +libdirac-dev_1.0.2-6_i386 +libdirectfb-dev_1.2.10.0-5_i386 +libdisasm-dev_0.23-5_i386 +libdiscid0-dev_0.2.2-3_i386 +libdiscover-dev_2.1.2-5.2_i386 +libdispatch-dev_0~svn197-3.1_i386 +libdisplaymigration0-dev_0.28-10_i386 +libdistorm64-dev_1.7.30-1_i386 +libdivecomputer-dev_0.1.0-3_i386 +libdjconsole-dev_0.1.3-1_i386 +libdjvulibre-dev_3.5.25.3-1_i386 +libdkim-dev_1:1.0.21-3_i386 +libdlm-dev_3.0.12-3.2+deb7u2_i386 +libdlmcontrol-dev_3.0.12-3.2+deb7u2_i386 +libdlrestrictions-dev_0.15.3_i386 +libdm0-dev_2.2.10-1_i386 +libdmalloc-dev_5.5.2-5_i386 +libdmapsharing-3.0-dev_2.9.15-1_i386 +libdmraid-dev_1.0.0.rc16-4.2_i386 +libdmtcpaware-dev_1.2.5-1_i386 +libdmtx-dev_0.7.2-2+build1_i386 +libdmx-dev_1:1.1.2-1+deb7u1_i386 +libdnet-dev_2.60_i386 +libdockapp-dev_1:0.5.0-3_i386 +libdolfin1.0-dev_1.0.0-7_i386 +libdoodle-dev_0.7.0-5_i386 +libdose2-ocaml-dev_1.4.2-4+b3_i386 +libdose3-ocaml-dev_3.0.2-3_i386 +libdotconf-dev_1.0.13-3_i386 +libdpkg-dev_1.16.12_i386 +libdpm-dev_1.8.2-1+b2_i386 +libdrawtk-dev_2.0-2_i386 +libdrizzle-dev_1:7.1.36-stable-1_all +libdrizzledmessage-dev_1:7.1.36-stable-1_i386 +libdrm-dev_2.4.40-1~deb7u2_i386 +libdrumstick-dev_0.5.0-3_i386 +libdsdp-dev_5.8-9.1_i386 +libdshconfig1-dev_0.20.13-1_i386 +libdspam7-dev_3.10.1+dfsg-11_i386 +libdssi-ocaml-dev_0.1.0-1+b1_i386 +libdssialsacompat-dev_1.0.8a-1_i386 +libdtools-ocaml-dev_0.3.0-1_i386 +libdts-dev_0.0.5-5_i386 +libdumb1-dev_1:0.9.3-5.4_i386 +libdumbnet-dev_1.12-3.1_i386 +libdune-common-dev_2.2.0-1_i386 +libdune-geometry-dev_2.2.0-1_i386 +libdune-grid-dev_2.2.0-1_i386 +libdune-istl-dev_2.2.0-1_all +libdune-localfunctions-dev_2.2.0-1_all +libduo-dev_1.8-1_i386 +libduo-dev_1.8.1-1~deb7u1_i386 +libduppy-ocaml-dev_0.4.2-1+b2_i386 +libdv4-dev_1.0.0-6_i386 +libdvb-dev_0.5.5.1-5.1_i386 +libdvbcsa-dev_1.1.0-2_i386 +libdvbpsi-dev_0.2.2-1_i386 +libdvdnav-dev_4.2.0+20120524-2_i386 +libdvdread-dev_4.2.0+20120521-2_i386 +libdvdread-dev_4.2.1-2~bpo70+1_i386 +libdw-dev_0.152-1+wheezy1_i386 +libdwarf-dev_20120410-2_i386 +libdx4-dev_1:4.4.4-4+b2_i386 +libdxflib-dev_2.2.0.0-8_i386 +libdynamite-dev_0.1.1-2_i386 +libeasy-format-ocaml-dev_1.0.0-1+b2_i386 +libeb16-dev_4.4.3-6_i386 +libebackend1.2-dev_3.4.4-3_i386 +libebml-dev_1.2.2-2_i386 +libebook1.2-dev_3.4.4-3_i386 +libecal1.2-dev_3.4.4-3_i386 +libecasoundc-dev_2.9.0-1_i386 +libecasoundc2.2-dev_2.9.0-1_all +libechonest-dev_1.2.1-1_i386 +libecm-dev_6.4.2-1_i386 +libecore-dev_1.2.0-2_i386 +libecpg-dev_9.1.13-0wheezy1_i386 +libecryptfs-dev_99-1_i386 +libedac-dev_0.18-1_i386 +libedata-book1.2-dev_3.4.4-3_i386 +libedata-cal1.2-dev_3.4.4-3_i386 +libedataserver1.2-dev_3.4.4-3_i386 +libedataserverui-3.0-dev_3.4.4-3_i386 +libedbus-dev_1.2.0-1_i386 +libedit-dev_2.11-20080614-5_i386 +libeditline-dev_1.12-6_i386 +libedje-dev_1.2.0-1_i386 +libee-dev_0.4.1-1_i386 +libeegdev-dev_0.2-3_i386 +libeet-dev_1.6.0-1_i386 +libefreet-dev_1.2.0-1_i386 +libegl1-mesa-dev_8.0.5-4+deb7u2_i386 +libeigen2-dev_2.0.17-1_i386 +libeigen3-dev_3.1.0-1_i386 +libeigen3-dev_3.2.1-2~bpo70+1_all +libeina-dev_1.2.0-2_i386 +libeiskaltdcpp-dev_2.2.9-3~bpo70+1_i386 +libelektra-cpp-dev_0.7.1-1_i386 +libelektra-dev_0.7.1-1_i386 +libelektratools-dev_0.7.1-1_i386 +libelemental-dev_1.2.0-8_i386 +libelementary-dev_0.7.0.55225-1_i386 +libelf-dev_0.152-1+wheezy1_i386 +libelfg0-dev_0.8.13-3_i386 +libeliom-ocaml-dev_2.2.2-1_i386 +libelk0-dev_3.99.8-2_i386 +libelmer-dev_6.1.0.svn.5396.dfsg2-2_i386 +libembryo-dev_1.2.0-1_i386 +libemos-dev_000382+dfsg-2_i386 +libenca-dev_1.13-4_i386 +libenchant-dev_1.6.0-7_i386 +libenet-dev_1.3.3-2_i386 +libepc-dev_0.4.4-1_i386 +libepc-ui-dev_0.4.4-1_i386 +libepr-api2-dev_2.2-2_all +libepsilon-dev_0.9.1-2_i386 +libept-dev_1.0.9_i386 +libepub-dev_0.2.1-2+b1_i386 +liberis-1.3-dev_1.3.19-5_i386 +liberuby-dev_1.0.5-2.1_i386 +libescpr-dev_1.1.1-2_i386 +libesd0-dev_0.2.41-10+b1_i386 +libesmtp-dev_1.0.6-1+b1_i386 +libespeak-dev_1.46.02-2_i386 +libestools2.1-dev_1:2.1~release-5_i386 +libestr-dev_0.1.1-2_i386 +libestr-dev_0.1.9-1~bpo70+1_i386 +libethos-dev_0.2.2-3_i386 +libethos-ui-dev_0.2.2-3_i386 +libetpan-dev_1.0-5_i386 +libetsf-io-dev_1.0.3-4+b1_i386 +libeurodec1-dev_20061220+dfsg3-2_i386 +libev-dev_1:4.11-1_i386 +libev-libevent-dev_1:4.11-1_all +libeval0-dev_0.29.6-2_i386 +libevas-dev_1.2.0-2_i386 +libevd-0.1-dev_0.1.20-2_i386 +libevent-dev_2.0.19-stable-3_i386 +libeventdb-dev_0.90-5_i386 +libevince-dev_3.4.0-3.1_i386 +libevocosm-dev_4.0.2-2.1_i386 +libevs-dev_1.4.2-3_i386 +libevtlog-dev_0.2.12-5_i386 +libewf-dev_20100226-1+b1_i386 +libewf-dev_20130416-3~bpo70+1_i386 +libexchangemapi-1.0-dev_3.4.4-1_i386 +libexempi-dev_2.2.0-1_i386 +libexif-dev_0.6.20-3_i386 +libexif-gtk-dev_0.3.5-5_i386 +libexiv2-dev_0.23-1_i386 +libexo-1-dev_0.6.2-5_i386 +libexodusii-dev_5.14.dfsg.1-2+b1_i386 +libexosip2-dev_3.6.0-4_i386 +libexpat-ocaml-dev_0.9.1+debian1-7+b2_i386 +libexpat1-dev_2.1.0-1+deb7u1_i386 +libexpect-ocaml-dev_0.0.2-1+b6_i386 +libexplain-dev_0.52.D002-1_i386 +libextlib-ocaml-dev_1.5.2-1+b1_i386 +libextractor-dev_1:0.6.3-5_i386 +libextractor-java-dev_0.6.0-6_i386 +libexttextcat-dev_3.2.0-2_i386 +libextunix-ocaml-dev_0.0.5-2_i386 +libeztrace-dev_0.7-2-4_i386 +libf2c2-dev_20090411-2_i386 +libfaad-dev_2.7-8_i386 +libfaad-ocaml-dev_0.3.0-1+b1_i386 +libfacile-ocaml-dev_1.1-8+b1_i386 +libfaifa-dev_0.2~svn82-1_i386 +libfakekey-dev_0.1-7_i386 +libfam-dev_2.7.0-17_i386 +libfann-dev_2.1.0~beta~dfsg-8_i386 +libfarstream-0.1-dev_0.1.2-1_i386 +libfastjet-dev_3.0.2+dfsg-2_i386 +libfastjet-fortran-dev_3.0.2+dfsg-2_i386 +libfastjetplugins-dev_3.0.2+dfsg-2_i386 +libfastjettools-dev_3.0.2+dfsg-2_i386 +libfauhdli-dev_20110812-1_i386 +libfcgi-dev_2.4.0-8.1_i386 +libfdt-dev_1.3.0-4_i386 +libfence-dev_3.0.12-3.2+deb7u2_i386 +libffado-dev_2.0.99+svn2171-2_i386 +libffcall1-dev_1.10+cvs20100619-2_i386 +libffi-dev_3.0.10-3_i386 +libffindex0-dev_0.9.6.1-1_i386 +libffmpegthumbnailer-dev_2.0.7-2_i386 +libffms2-dev_2.17-1_i386 +libfftw3-dev_3.3.2-3.1_i386 +libfftw3-mpi-dev_3.3.2-3.1_i386 +libfields-camlp4-dev_107.01-1+b2_i386 +libfileutils-ocaml-dev_0.4.2-1+b2_i386 +libfindlib-ocaml-dev_1.3.1-1_i386 +libfiredns-dev_0.9.12+dfsg-3_i386 +libfirestring-dev_0.9.12-8_i386 +libfishsound1-dev_1.0.0-1.1_i386 +libfiu-dev_0.90-3_i386 +libfixposix-dev_20110316.git47f17f7-1_i386 +libfko0-dev_2.0.0rc2-2+deb7u2_i386 +libfko2-dev_2.5.1-1~bpo70+1_i386 +libflac++-dev_1.2.1-6_i386 +libflac-dev_1.2.1-6_i386 +libflac-ocaml-dev_0.1.1-1_i386 +libflake-dev_0.11-2_i386 +libflann-dev_1.7.1-4_i386 +libflatzebra-dev_0.1.5-4+b1_i386 +libflickrnet-cil-dev_1:2.2.0-4_all +libflorist2011-dev_2011-1_i386 +libflowcanvas-dev_0.7.1+dfsg0-0.2_i386 +libfltk1.1-dev_1.1.10-14_i386 +libfltk1.3-dev_1.3.0-8_i386 +libfluidsynth-dev_1.1.5-2_i386 +libfm-dev_0.1.17-2.1_i386 +libfm-dev_1.1.2.2-1~bpo70+1_i386 +libfm-gtk-dev_1.1.2.2-1~bpo70+1_i386 +libfolia1-dev_0.9-2_i386 +libfolks-dev_0.6.9-1+b1_i386 +libfolks-eds-dev_0.6.9-1+b1_i386 +libfolks-telepathy-dev_0.6.9-1+b1_i386 +libfontconfig1-dev_2.9.0-7.1_i386 +libfontenc-dev_1:1.1.1-1_i386 +libfontforge-dev_0.0.20120101+git-2_i386 +libforms-dev_1.0.93sp1-2_i386 +libformsgl-dev_1.0.93sp1-2_i386 +libfox-1.6-dev_1.6.45-1_i386 +libfprint-dev_1:0.4.0-4-gdfff16f-4_i386 +libfreecell-solver-dev_3.12.0-1_i386 +libfreefem++-dev_3.19.1-1_i386 +libfreefem-dev_3.5.8-5_i386 +libfreehdl0-dev_0.0.7-1.1_i386 +libfreeimage-dev_3.15.1-1+b1_i386 +libfreeipmi-dev_1.1.5-3_i386 +libfreenect-dev_1:0.1.2+dfsg-6_i386 +libfreeradius-client-dev_1.1.6-7~bpo70+1_i386 +libfreeradius-dev_2.1.12+dfsg-1.2_i386 +libfreerdp-dev_1.0.1-1.1+deb7u3_i386 +libfreetype6-dev_2.4.9-1.1_i386 +libfreexl-dev_1.0.0b-1_i386 +libfribidi-dev_0.19.2-3_i386 +libfs-dev_2:1.0.4-1+deb7u1_i386 +libfso-glib-dev_2012.05.24.1-1.1_i386 +libfsobasics-dev_0.11.0-1.1_i386 +libfsoframework-dev_0.11.0-1.1_i386 +libfsoresource-dev_0.11.0-1.1_i386 +libfsosystem-dev_0.11.0-1_i386 +libfsotransport-dev_0.11.1-2.1_i386 +libfsplib-dev_0.11-2_i386 +libfstrcmp-dev_0.4.D001-1+deb7u1_i386 +libftdi-dev_0.20-1+b1_i386 +libftdipp-dev_0.20-1+b1_i386 +libftgl-dev_2.1.3~rc5-4_i386 +libfuntools-dev_1.4.4-3_i386 +libfuse-dev_2.9.0-2+deb7u1_i386 +libfuzzy-dev_2.7-2_i386 +libfxt-dev_0.2.6-2_i386 +libg15-dev_1.2.7-2_i386 +libg15daemon-client-dev_1.9.5.3-8.2_i386 +libg15render-dev_1.3.0~svn316-2.2_i386 +libg2-dev_0.72-2.1_i386 +libg3d-dev_0.0.8-17_i386 +libga-dev_2.4.7-3_i386 +libgadap-dev_2.0-1_i386 +libgadu-dev_1:1.11.2-1+deb7u1_i386 +libgail-3-dev_3.4.2-7_i386 +libgail-dev_2.24.10-2_i386 +libgalax-ocaml-dev_1.1-10+b3_i386 +libgambc4-dev_4.2.8-1.1_i386 +libgamin-dev_0.1.10-4.1_i386 +libgammu-dev_1.31.90-1+b1_i386 +libganglia1-dev_3.3.8-1+nmu1_i386 +libganv-dev_0~svn4468~dfsg0-1_i386 +libgarcon-1-0-dev_0.1.12-1_i386 +libgarmin-dev_0~svn320-3_i386 +libgatos-dev_0.0.5-19_i386 +libgavl-dev_1.4.0-1_i386 +libgavl-ocaml-dev_0.1.4-1+b1_i386 +libgbm-dev_8.0.5-4+deb7u2_i386 +libgc-dev_1:7.1-9.1_i386 +libgcal-dev_0.9.6-3_i386 +libgccxml-dev_0.9.0+cvs20120420-4_i386 +libgcgi-dev_0.9.5.dfsg-7_i386 +libgcj12-dev_4.6.3-1_i386 +libgcj13-dev_4.7.2-3_i386 +libgck-1-dev_3.4.1-3_i386 +libgconf-bridge-dev_0.1-2.2_i386 +libgconf2-dev_3.2.5-1+build1_i386 +libgconf2.0-cil-dev_2.24.2-3_all +libgconfmm-2.6-dev_2.28.0-1_i386 +libgcr-3-dev_3.4.1-3_i386 +libgcroots-dev_0.8.5-2.1_i386 +libgcrypt11-dev_1.5.0-5+deb7u1_i386 +libgctp-dev_1.0-1_i386 +libgd-gd2-noxpm-ocaml-dev_1.0~alpha5-5_i386 +libgd2-noxpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgd2-xpm-dev_2.0.36~rc1~dfsg-6.1_i386 +libgda-5.0-dev_5.0.3-2_i386 +libgdal-dev_1.9.0-3.1_i386 +libgdal1-dev_1.9.0-3.1_all +libgdata-cil-dev_2.1.0.0-1_all +libgdata-dev_0.12.0-1_i386 +libgdb-dev_7.4.1+dfsg-0.1_i386 +libgdbm-dev_1.8.3-11_i386 +libgdchart-gd2-noxpm-dev_0.11.5-7+b1_i386 +libgdchart-gd2-xpm-dev_0.11.5-7+b1_i386 +libgdcm2-dev_2.2.0-14.1_i386 +libgdf-dev_0.1.2-2_i386 +libgdict-1.0-dev_3.4.0-2_i386 +libgdk-pixbuf2.0-dev_2.26.1-1_i386 +libgdkcutter-pixbuf-dev_1.1.7-1.2_i386 +libgdl-3-dev_3.4.2-1_i386 +libgdome2-cpp-smart-dev_0.2.6-6+b1_i386 +libgdome2-dev_0.8.1+debian-4.1_i386 +libgdome2-ocaml-dev_0.2.6-6+b1_i386 +libgdu-dev_3.0.2-3_i386 +libgdu-gtk-dev_3.0.2-3_i386 +libgeant321-2-dev_1:3.21.14.dfsg-10_i386 +libgearman-dev_0.33-2_i386 +libgecode-dev_3.7.3-1_i386 +libgeda-dev_1:1.6.2-4.3_i386 +libgee-dev_0.6.4-2_i386 +libgegl-dev_0.2.0-2+nmu1_i386 +libgeier-dev_0.13-1+b1_i386 +libgenders0-dev_1.18-1_i386 +libgenome-1.3-0-dev_1.3.1-3_i386 +libgensec-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libgeoclue-dev_0.12.0-4_i386 +libgeocode-glib-dev_0.99.0-1_i386 +libgeographiclib-dev_1.21-1_i386 +libgeoip-dev_1.4.8+dfsg-3_i386 +libgeoip-dev_1.5.0-3~bpo70+1_i386 +libgeomview-dev_1.9.4-3_i386 +libgeos++-dev_3.3.3-1.1_i386 +libgeos-dev_3.3.3-1.1_i386 +libgeotiff-dev_1.3.0+dfsg-3_i386 +libgeotranz3-dev_3.1-2.1_i386 +libges-0.10-dev_0.10.1-2_i386 +libgetdata-dev_0.7.3-6_i386 +libgetfem++-dev_4.1.1+dfsg1-11_i386 +libgetopt++-dev_0.0.2-p22-3_i386 +libgetopt-ocaml-dev_0.0.20040811-10+b3_i386 +libgettext-ocaml-dev_0.3.4-1+b2_i386 +libgettextpo-dev_0.18.3-1~bpo7+1_i386 +libgexiv2-dev_0.4.1-3_i386 +libgfarm-dev_2.4.1-1.1_i386 +libgflags-dev_2.0-1_i386 +libgfshare-dev_1.0.5-2_i386 +libghc-acid-state-dev_0.6.3-1+b2_i386 +libghc-active-dev_0.1.0.1-2+b2_i386 +libghc-adjunctions-dev_2.4.0.2-1_i386 +libghc-aeson-dev_0.6.0.2-1+b4_i386 +libghc-agda-dev_2.3.0.1-2_i386 +libghc-algebra-dev_2.1.1.2-1_i386 +libghc-alut-dev_2.1.0.2-4+b1_i386 +libghc-ami-dev_0.1-1+b5_i386 +libghc-ansi-terminal-dev_0.5.5-3+b1_i386 +libghc-ansi-wl-pprint-dev_0.6.4-1+b1_i386 +libghc-arrows-dev_0.4.4.0-3+b1_i386 +libghc-asn1-data-dev_0.6.1.3-2+b3_i386 +libghc-async-dev_2.0.1.3-1~bpo70+1_i386 +libghc-attempt-dev_0.4.0-1+b2_i386 +libghc-attoparsec-conduit-dev_0.4.0.1-1_i386 +libghc-attoparsec-dev_0.10.1.1-2+b1_i386 +libghc-attoparsec-enumerator-dev_0.3-3+b3_i386 +libghc-augeas-dev_0.6.1-1_i386 +libghc-authenticate-dev_1.2.1.1-2+b1_i386 +libghc-base-unicode-symbols-dev_0.2.2.3-1+b1_i386 +libghc-base16-bytestring-dev_0.1.1.4-2+b1_i386 +libghc-base64-bytestring-dev_0.1.1.1-2_i386 +libghc-base64-bytestring-dev_1.0.0.1-1~bpo70+1_i386 +libghc-bifunctors-dev_0.1.3.3-1+b1_i386 +libghc-binary-shared-dev_0.8.1-1+b1_i386 +libghc-bindings-dsl-dev_1.0.15-1+b1_i386 +libghc-bindings-gpgme-dev_0.1.4-1_i386 +libghc-bindings-libzip-dev_0.10-2_i386 +libghc-bitarray-dev_0.0.1-2+b1_i386 +libghc-blaze-builder-conduit-dev_0.4.0.2-1_i386 +libghc-blaze-builder-dev_0.3.1.0-1+b2_i386 +libghc-blaze-builder-enumerator-dev_0.2.0.4-1+b1_i386 +libghc-blaze-html-dev_0.4.3.1-3+b2_i386 +libghc-blaze-markup-dev_0.5.1.0-1_i386 +libghc-blaze-textual-dev_0.2.0.6-2+b2_i386 +libghc-bloomfilter-dev_1.2.6.8-1_i386 +libghc-boolean-dev_0.0.1-2+b1_i386 +libghc-boomerang-dev_1.3.1-1_i386 +libghc-brainfuck-dev_0.1-2+b2_i386 +libghc-byteorder-dev_1.0.3-2+b1_i386 +libghc-bytestring-lexing-dev_0.4.0-1+b1_i386 +libghc-bytestring-mmap-dev_0.2.2-2+b1_i386 +libghc-bytestring-nums-dev_0.3.5-2+b1_i386 +libghc-bytestring-show-dev_0.3.5.1-1+b1_i386 +libghc-bzlib-dev_0.5.0.3-2+b1_i386 +libghc-cabal-file-th-dev_0.2.2-1_i386 +libghc-cairo-dev_0.12.3-1+b1_i386 +libghc-case-insensitive-dev_0.4.0.1-2+b2_i386 +libghc-categories-dev_1.0.3-1+b1_i386 +libghc-cautious-file-dev_1.0.1-1_i386 +libghc-cereal-conduit-dev_0.5-1+b1_i386 +libghc-cereal-dev_0.3.5.2-1_i386 +libghc-certificate-dev_1.2.3-2_i386 +libghc-cgi-dev_3001.1.8.2-2+b3_i386 +libghc-chart-dev_0.15-1+b2_i386 +libghc-chell-dev_0.3-1_i386 +libghc-citeproc-hs-dev_0.3.4-1+b4_i386 +libghc-clientsession-dev_0.7.5-3+b1_i386 +libghc-clock-dev_0.2.0.0-2+b1_i386 +libghc-cmdargs-dev_0.9.5-1+b1_i386 +libghc-colour-dev_2.3.3-1+b1_i386 +libghc-comonad-dev_1.1.1.5-1+b1_i386 +libghc-comonad-transformers-dev_2.1.1.1-1+b1_i386 +libghc-comonads-fd-dev_2.1.1.2-1+b1_i386 +libghc-conduit-dev_0.4.2-2_i386 +libghc-configfile-dev_1.0.6-4+b3_i386 +libghc-configurator-dev_0.2.0.0-1+b2_i386 +libghc-contravariant-dev_0.2.0.2-1+b1_i386 +libghc-convertible-dev_1.0.11.0-3+b3_i386 +libghc-cookie-dev_0.4.0-1+b3_i386 +libghc-cpphs-dev_1.13.3-2+b1_i386 +libghc-cprng-aes-dev_0.2.3-3+b4_i386 +libghc-cpu-dev_0.1.1-1_i386 +libghc-criterion-dev_0.6.0.1-3+b4_i386 +libghc-crypto-api-dev_0.10.2-1+b2_i386 +libghc-crypto-conduit-dev_0.3.2-1+b1_i386 +libghc-crypto-dev_4.2.4-1+b1_i386 +libghc-crypto-pubkey-types-dev_0.1.1-1+b3_i386 +libghc-cryptocipher-dev_0.3.5-1+b1_i386 +libghc-cryptohash-dev_0.7.5-1+b2_i386 +libghc-css-text-dev_0.1.1-3+b2_i386 +libghc-csv-conduit-dev_0.2-1_i386 +libghc-csv-dev_0.1.2-2+b3_i386 +libghc-curl-dev_1.3.7-1+b1_i386 +libghc-darcs-dev_2.8.1-1+b1_i386 +libghc-data-accessor-dev_0.2.2.2-1+b1_i386 +libghc-data-accessor-mtl-dev_0.2.0.3-1+b1_i386 +libghc-data-accessor-template-dev_0.2.1.9-1+b2_i386 +libghc-data-binary-ieee754-dev_0.4.2.1-3+b1_i386 +libghc-data-default-dev_0.4.0-1_i386 +libghc-data-inttrie-dev_0.0.7-1+b1_i386 +libghc-data-lens-dev_2.10.0-1+b1_i386 +libghc-data-memocombinators-dev_0.4.3-1+b1_i386 +libghc-dataenc-dev_0.14.0.3-1+b1_i386 +libghc-datetime-dev_0.2.1-3_i386 +libghc-dbus-dev_0.10.3-1_i386 +libghc-debian-dev_3.64-3_i386 +libghc-diagrams-cairo-dev_0.5.0.2-1_i386 +libghc-diagrams-core-dev_0.5.0.1-1+b1_i386 +libghc-diagrams-dev_0.5-2_all +libghc-diagrams-lib-dev_0.5-2_i386 +libghc-diff-dev_0.1.3-1+b1_i386 +libghc-digest-dev_0.0.1.0-1+b1_i386 +libghc-dimensional-dev_0.10.1.2-2+b1_i386 +libghc-directory-tree-dev_0.10.0-2+b1_i386 +libghc-distributive-dev_0.2.2-1+b1_i386 +libghc-dlist-dev_0.5-3+b1_i386 +libghc-doctest-dev_0.9.1-1~bpo70+1_i386 +libghc-download-curl-dev_0.1.3-3+b3_i386 +libghc-dpkg-dev_0.0.3-1_i386 +libghc-dyre-dev_0.8.7-1_i386 +libghc-edison-api-dev_1.2.1-18+b1_i386 +libghc-edison-core-dev_1.2.1.3-9+b1_i386 +libghc-edit-distance-dev_0.2.1-2_i386 +libghc-editline-dev_0.2.1.0-5+b1_i386 +libghc-ekg-dev_0.3.1.0-1+b2_i386 +libghc-email-validate-dev_0.2.8-1+b3_i386 +libghc-entropy-dev_0.2.1-2+b1_i386 +libghc-enumerator-dev_0.4.19-1+b1_i386 +libghc-erf-dev_2.0.0.0-2+b1_i386 +libghc-event-list-dev_0.1.0.1-1+b1_i386 +libghc-exception-transformers-dev_0.3.0.2-1+b1_i386 +libghc-exceptions-dev_0.5-1~bpo70+1_i386 +libghc-executable-path-dev_0.0.3-1+b1_i386 +libghc-explicit-exception-dev_0.1.7-1+b1_i386 +libghc-failure-dev_0.2.0.1-1+b1_i386 +libghc-fast-logger-dev_0.0.2-1+b2_i386 +libghc-fastcgi-dev_3001.0.2.3-3+b3_i386 +libghc-fclabels-dev_1.1.3-1+b1_i386 +libghc-feed-dev_0.3.8-3_i386 +libghc-fgl-dev_5.4.2.4-2+b2_i386 +libghc-file-embed-dev_0.0.4.4-1_i386 +libghc-filemanip-dev_0.3.5.2-2+b2_i386 +libghc-filestore-dev_0.5-1_i386 +libghc-filesystem-conduit-dev_0.4.0-1_i386 +libghc-free-dev_2.1.1.1-1+b1_i386 +libghc-ftphs-dev_1.0.8-1+b3_i386 +libghc-gconf-dev_0.12.1-1+b1_i386 +libghc-gd-dev_3000.7.3-1_i386 +libghc-generic-deriving-dev_1.4.0-2~bpo70+1_i386 +libghc-ghc-events-dev_0.4.0.0-2+b1_i386 +libghc-ghc-mtl-dev_1.0.1.1-1+b3_i386 +libghc-ghc-paths-dev_0.1.0.8-2+b1_i386 +libghc-ghc-syb-utils-dev_0.2.1.0-1+b3_i386 +libghc-gio-dev_0.12.3-1+b1_i386 +libghc-github-dev_0.4.0-2_i386 +libghc-gitit-dev_0.10.0.1-1+b1_i386 +libghc-glade-dev_0.12.1-1+b3_i386 +libghc-glfw-dev_0.5.0.1-1+b1_i386 +libghc-glib-dev_0.12.2-1+b1_i386 +libghc-glut-dev_2.1.2.2-1_i386 +libghc-gnuidn-dev_0.2-2+b2_i386 +libghc-gnutls-dev_0.1.2-1+b1_i386 +libghc-gnutls-dev_0.1.4-3~bpo70+1_i386 +libghc-gsasl-dev_0.3.4-1+b1_i386 +libghc-gstreamer-dev_0.12.1-1+b2_i386 +libghc-gtk-dev_0.12.3-1+b2_i386 +libghc-gtkglext-dev_0.12.1-1+b3_i386 +libghc-gtksourceview2-dev_0.12.3-1+b3_i386 +libghc-haddock-dev_2.10.0-1+b2_i386 +libghc-hakyll-dev_3.2.7.2-1+b5_i386 +libghc-hamlet-dev_1.0.1.3-1+b1_i386 +libghc-happstack-dev_7.0.0-1+b1_i386 +libghc-happstack-server-dev_7.0.1-1+b1_i386 +libghc-harp-dev_0.4-3+b1_i386 +libghc-hashable-dev_1.1.2.3-1+b2_i386 +libghc-hashed-storage-dev_0.5.9-2+b2_i386 +libghc-hashmap-dev_1.3.0.1-1+b2_i386 +libghc-hashtables-dev_1.0.1.4-1+b1_i386 +libghc-haskeline-dev_0.6.4.7-1+b1_i386 +libghc-haskell-lexer-dev_1.0-3+b1_i386 +libghc-haskell-src-dev_1.0.1.5-1+b2_i386 +libghc-haskelldb-dev_2.1.1-5+b1_i386 +libghc-haskelldb-hdbc-dev_2.1.0-4_i386 +libghc-haskelldb-hdbc-odbc-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-postgresql-dev_2.1.0-3_i386 +libghc-haskelldb-hdbc-sqlite3-dev_2.1.0-3_i386 +libghc-haskore-dev_0.2.0.3-2_i386 +libghc-hastache-dev_0.3.3-2+b3_i386 +libghc-haxml-dev_1:1.22.5-2+b2_i386 +libghc-haxr-dev_3000.8.5-1+b3_i386 +libghc-hcard-dev_0.0-2+b2_i386 +libghc-hcwiid-dev_0.0.1-3+b1_i386 +libghc-hdbc-dev_2.3.1.1-1+b3_i386 +libghc-hdbc-odbc-dev_2.2.3.0-5+b3_i386 +libghc-hdbc-postgresql-dev_2.3.2.1-1+b3_i386 +libghc-hdbc-sqlite3-dev_2.3.3.0-1+b3_i386 +libghc-hfuse-dev_0.2.4.1-1_i386 +libghc-highlighting-kate-dev_0.5.1-1_i386 +libghc-hinotify-dev_0.3.2-1+b1_i386 +libghc-hint-dev_0.3.3.4-2+b4_i386 +libghc-hipmunk-dev_5.2.0.8-1+b1_i386 +libghc-hjavascript-dev_0.4.7-3+b1_i386 +libghc-hjscript-dev_0.5.0-3+b2_i386 +libghc-hjsmin-dev_0.1.1-1+b2_i386 +libghc-hlint-dev_1.8.28-1+b3_i386 +libghc-hoauth-dev_0.3.4-1+b1_i386 +libghc-hostname-dev_1.0-4+b1_i386 +libghc-hs-bibutils-dev_4.12-5+b2_i386 +libghc-hs3-dev_0.5.6-2+b4_i386 +libghc-hscolour-dev_1.19-3+b1_i386 +libghc-hscurses-dev_1.4.1.0-1+b2_i386 +libghc-hsemail-dev_1.7.1-2+b3_i386 +libghc-hsh-dev_2.0.3-6+b3_i386 +libghc-hslogger-dev_1.1.4+dfsg1-2+b3_i386 +libghc-hsp-dev_0.6.1-2+b3_i386 +libghc-hspec-dev_1.1.0-1+b1_i386 +libghc-hsql-dev_1.8.1-4_i386 +libghc-hsql-mysql-dev_1.8.1-4+b1_i386 +libghc-hsql-odbc-dev_1.8.1.1-2_i386 +libghc-hsql-postgresql-dev_1.8.1-3_i386 +libghc-hsql-sqlite3-dev_1.8.1-2_i386 +libghc-hssyck-dev_0.50-2+b2_i386 +libghc-hstringtemplate-dev_0.6.8-1_i386 +libghc-hsx-dev_0.9.1-3_i386 +libghc-html-conduit-dev_0.0.1-2_i386 +libghc-html-dev_1.0.1.2-5+b1_i386 +libghc-http-conduit-dev_1.4.1.6-3_i386 +libghc-http-date-dev_0.0.2-1+b2_i386 +libghc-http-dev_1:4000.2.3-1+b2_i386 +libghc-http-types-dev_0.6.11-1_i386 +libghc-hunit-dev_1.2.4.2-2+b1_i386 +libghc-hxt-cache-dev_9.0.2-2+b3_i386 +libghc-hxt-charproperties-dev_9.1.1-2+b1_i386 +libghc-hxt-curl-dev_9.1.1-1+b4_i386 +libghc-hxt-dev_9.2.2-2+b3_i386 +libghc-hxt-http-dev_9.1.4-2+b3_i386 +libghc-hxt-regex-xmlschema-dev_9.0.4-2+b3_i386 +libghc-hxt-relaxng-dev_9.1.4-1+b3_i386 +libghc-hxt-tagsoup-dev_9.1.1-1+b4_i386 +libghc-hxt-unicode-dev_9.0.2-2+b1_i386 +libghc-hxt-xpath-dev_9.1.2-1+b4_i386 +libghc-hxt-xslt-dev_9.1.1-1+b3_i386 +libghc-iconv-dev_0.4.1.0-2+b1_i386 +libghc-ieee754-dev_0.7.3-1+b1_i386 +libghc-ifelse-dev_0.85-4+b1_i386 +libghc-io-choice-dev_0.0.1-1+b3_i386 +libghc-io-storage-dev_0.3-2+b1_i386 +libghc-iospec-dev_0.2.5-1+b2_i386 +libghc-irc-dev_0.5.0.0-1+b3_i386 +libghc-iteratee-dev_0.8.8.2-2+b1_i386 +libghc-ixset-dev_1.0.3-2+b1_i386 +libghc-json-dev_0.5-2+b2_i386 +libghc-keys-dev_2.1.3.2-1+b1_i386 +libghc-knob-dev_0.1.1-1_i386 +libghc-lambdabot-utils-dev_4.2.1-3+b3_i386 +libghc-language-c-dev_0.4.2-2+b2_i386 +libghc-language-haskell-extract-dev_0.2.1-4+b1_i386 +libghc-language-javascript-dev_0.5.4-1+b2_i386 +libghc-largeword-dev_1.0.1-2+b1_i386 +libghc-lazysmallcheck-dev_0.6-1+b1_i386 +libghc-ldap-dev_0.6.6-4.1+b1_i386 +libghc-leksah-server-dev_0.12.0.4-3_i386 +libghc-libtagc-dev_0.12.0-2+b1_i386 +libghc-libxml-sax-dev_0.7.2-2+b1_i386 +libghc-libzip-dev_0.10-1+b2_i386 +libghc-lifted-base-dev_0.1.1-1+b1_i386 +libghc-listlike-dev_3.1.4-1+b1_i386 +libghc-llvm-base-dev_3.0.1.0-1_i386 +libghc-llvm-dev_3.0.1.0-1+b1_i386 +libghc-logict-dev_0.5.0.1-1+b1_i386 +libghc-ltk-dev_0.12.0.0-2+b1_i386 +libghc-maccatcher-dev_2.1.5-2+b3_i386 +libghc-magic-dev_1.0.8-8+b1_i386 +libghc-markov-chain-dev_0.0.3.2-1+b1_i386 +libghc-math-functions-dev_0.1.1.0-2+b2_i386 +libghc-maths-dev_0.4.3-1+b1_i386 +libghc-maybet-dev_0.1.2-3+b2_i386 +libghc-mbox-dev_0.1-2+b1_i386 +libghc-memotrie-dev_0.5-1_i386 +libghc-mersenne-random-dev_1.0.0.1-2+b1_i386 +libghc-midi-dev_0.2.0.1-1+b1_i386 +libghc-mime-mail-dev_0.4.1.1-2+b3_i386 +libghc-missingh-dev_1.1.0.3-6+b3_i386 +libghc-mmap-dev_0.5.7-2+b1_i386 +libghc-monad-control-dev_0.3.1.3-1+b1_i386 +libghc-monad-loops-dev_0.3.2.0-1_i386 +libghc-monad-par-dev_0.1.0.3-2+b1_i386 +libghc-monadcatchio-mtl-dev_0.3.0.4-2+b2_i386 +libghc-monadcatchio-transformers-dev_0.3.0.0-2+b1_i386 +libghc-monadcryptorandom-dev_0.4.1-1+b2_i386 +libghc-monadrandom-dev_0.1.6-2+b2_i386 +libghc-monads-tf-dev_0.1.0.0-1+b2_i386 +libghc-monoid-transformer-dev_0.0.2-3+b1_i386 +libghc-mtl-dev_2.1.1-1_i386 +libghc-mtlparse-dev_0.1.2-2+b2_i386 +libghc-murmur-hash-dev_0.1.0.5-2+b1_i386 +libghc-mwc-random-dev_0.11.0.0-4+b1_i386 +libghc-ncurses-dev_0.2.1-1+b1_i386 +libghc-netwire-dev_3.1.0-2+b5_i386 +libghc-network-conduit-dev_0.4.0.1-2_i386 +libghc-network-dev_2.3.0.13-1+b2_i386 +libghc-network-info-dev_0.2.0.1-2~bpo70+1_i386 +libghc-network-multicast-dev_0.0.10-1~bpo70+1_i386 +libghc-network-protocol-xmpp-dev_0.4.3-1_i386 +libghc-network-protocol-xmpp-dev_0.4.4-2~bpo70+1_i386 +libghc-newtype-dev_0.2-1_i386 +libghc-non-negative-dev_0.1-2+b1_i386 +libghc-numbers-dev_2009.8.9-2+b1_i386 +libghc-numeric-quest-dev_0.2-1+b1_i386 +libghc-numinstances-dev_1.0-2+b1_i386 +libghc-numtype-dev_1.0-2+b1_i386 +libghc-oeis-dev_0.3.1-2+b3_i386 +libghc-openal-dev_1.3.1.3-4+b1_i386 +libghc-opengl-dev_2.2.3.1-1+b1_i386 +libghc-openpgp-asciiarmor-dev_0.1-1+b2_i386 +libghc-options-dev_0.1.1-1_i386 +libghc-pandoc-dev_1.9.4.2-2_i386 +libghc-pandoc-types-dev_1.9.1-1+b2_i386 +libghc-pango-dev_0.12.2-1+b2_i386 +libghc-parallel-dev_3.2.0.2-2+b1_i386 +libghc-parseargs-dev_0.1.3.2-2+b1_i386 +libghc-parsec2-dev_2.1.0.1-6+b1_i386 +libghc-parsec3-dev_3.1.2-1+b3_i386 +libghc-pastis-dev_0.1.2-2+b3_i386 +libghc-path-pieces-dev_0.1.0-1+b2_i386 +libghc-patience-dev_0.1.1-1_i386 +libghc-pcre-light-dev_0.4-3+b1_i386 +libghc-pem-dev_0.1.1-1+b3_i386 +libghc-persistent-dev_0.9.0.4-2_i386 +libghc-persistent-sqlite-dev_0.9.0.2-2_i386 +libghc-persistent-template-dev_0.9.0.2-1_i386 +libghc-polyparse-dev_1.7-1+b2_i386 +libghc-pool-conduit-dev_0.1.0.2-1_i386 +libghc-postgresql-libpq-dev_0.8.2-1_i386 +libghc-postgresql-simple-dev_0.1.4.3-1_i386 +libghc-pretty-show-dev_1.1.1-4+b1_i386 +libghc-primes-dev_0.2.1.0-2+b1_i386 +libghc-primitive-dev_0.4.1-1+b1_i386 +libghc-psqueue-dev_1.1-2+b1_i386 +libghc-puremd5-dev_2.1.0.3-2+b4_i386 +libghc-pwstore-fast-dev_2.2-2+b4_i386 +libghc-quickcheck1-dev_1.2.0.1-2+b1_i386 +libghc-quickcheck2-dev_2.4.2-1+b1_i386 +libghc-random-dev_1.0.1.1-1+b1_i386 +libghc-random-shuffle-dev_0.0.3-2+b2_i386 +libghc-ranged-sets-dev_0.3.0-2+b1_i386 +libghc-ranges-dev_0.2.4-2+b1_i386 +libghc-reactive-banana-dev_0.6.0.0-1+b3_i386 +libghc-readline-dev_1.0.1.0-3+b1_i386 +libghc-recaptcha-dev_0.1-4+b3_i386 +libghc-regex-base-dev_0.93.2-2+b2_i386 +libghc-regex-compat-dev_0.95.1-2+b1_i386 +libghc-regex-pcre-dev_0.94.2-2+b1_i386 +libghc-regex-posix-dev_0.95.1-2+b1_i386 +libghc-regex-tdfa-dev_1.1.8-2+b1_i386 +libghc-regex-tdfa-utf8-dev_1.0-5+b3_i386 +libghc-regexpr-dev_0.5.4-2+b2_i386 +libghc-representable-functors-dev_2.4.0.2-1+b1_i386 +libghc-representable-tries-dev_2.4.0.2-1_i386 +libghc-resource-pool-dev_0.2.1.0-2+b4_i386 +libghc-resourcet-dev_0.3.2.1-1+b1_i386 +libghc-rsa-dev_1.2.1.0-1+b1_i386 +libghc-safe-dev_0.3.3-1+b1_i386 +libghc-safecopy-dev_0.6.1-1+b1_i386 +libghc-safesemaphore-dev_0.9.0-1~bpo70+1_i386 +libghc-sdl-dev_0.6.3-1+b1_i386 +libghc-sdl-gfx-dev_0.6.0-3+b1_i386 +libghc-sdl-image-dev_0.6.1-3+b1_i386 +libghc-sdl-mixer-dev_0.6.1-3+b1_i386 +libghc-sdl-ttf-dev_0.6.1-3+b1_i386 +libghc-semigroupoids-dev_1.3.1.2-1+b1_i386 +libghc-semigroups-dev_0.8.3.2-1_i386 +libghc-sendfile-dev_0.7.6-1+b2_i386 +libghc-sha-dev_1.5.0.1-1_i386 +libghc-shakespeare-css-dev_1.0.1.2-1+b1_i386 +libghc-shakespeare-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-i18n-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-js-dev_1.0.0.2-1+b1_i386 +libghc-shakespeare-text-dev_1.0.0.2-1+b1_i386 +libghc-shellac-dev_0.9.5.1-2+b2_i386 +libghc-show-dev_0.4.1.2-1+b2_i386 +libghc-silently-dev_1.1.4-1+b2_i386 +libghc-simple-sendfile-dev_0.2.3-1+b2_i386 +libghc-simpleea-dev_0.1.1-2+b2_i386 +libghc-simpleirc-dev_0.2.1-2+b3_i386 +libghc-skein-dev_0.1.0.7-2+b1_i386 +libghc-smallcheck-dev_0.6-1+b1_i386 +libghc-smtpclient-dev_1.0.4-3+b3_i386 +libghc-snap-core-dev_0.8.1-1+b4_i386 +libghc-snap-server-dev_0.8.1.1-1_i386 +libghc-socks-dev_0.4.1-1+b4_i386 +libghc-split-dev_0.1.4.2-2_i386 +libghc-src-exts-dev_1.11.1-3+b1_i386 +libghc-statevar-dev_1.0.0.0-2+b1_i386 +libghc-static-hash-dev_0.0.1-3+b2_i386 +libghc-statistics-dev_0.10.1.0-2+b1_i386 +libghc-stm-dev_2.3-1_i386 +libghc-stream-dev_0.4.6-1+b1_i386 +libghc-strict-concurrency-dev_0.2.4.1-2+b1_i386 +libghc-strict-dev_0.3.2-2+b1_i386 +libghc-strptime-dev_1.0.6-1_i386 +libghc-svgcairo-dev_0.12.1-1+b2_i386 +libghc-syb-dev_0.3.6.1-1_i386 +libghc-syb-with-class-dev_0.6.1.3-1+b1_i386 +libghc-syb-with-class-instances-text-dev_0.0.1-3+b2_i386 +libghc-system-fileio-dev_0.3.8-1_i386 +libghc-system-filepath-dev_0.4.6-1+b2_i386 +libghc-tagged-dev_0.4.2.1-1_i386 +libghc-tagsoup-dev_0.12.6-1+b3_i386 +libghc-tagstream-conduit-dev_0.3.2-1_i386 +libghc-tar-dev_0.3.2.0-2+b1_i386 +libghc-template-dev_0.2.0.7-1+b1_i386 +libghc-temporary-dev_1.1.2.3-1+b1_i386 +libghc-terminfo-dev_0.3.2.3-1+b1_i386 +libghc-test-framework-dev_0.6-1+b1_i386 +libghc-test-framework-hunit-dev_0.2.7-1+b3_i386 +libghc-test-framework-quickcheck2-dev_0.2.12.1-1+b1_i386 +libghc-test-framework-th-dev_0.2.2-5_i386 +libghc-test-framework-th-prime-dev_0.0.5-1_i386 +libghc-testpack-dev_2.1.1-1+b2_i386 +libghc-texmath-dev_0.6.0.6-1+b2_i386 +libghc-text-dev_0.11.2.0-1_i386 +libghc-text-icu-dev_0.6.3.4-2+b2_i386 +libghc-tinyurl-dev_0.1.0-2+b3_i386 +libghc-tls-dev_0.9.5-1+b4_i386 +libghc-tls-extra-dev_0.4.6.1-2_i386 +libghc-tokyocabinet-dev_0.0.5-5+b3_i386 +libghc-transformers-base-dev_0.4.1-2+b2_i386 +libghc-transformers-dev_0.3.0.0-1_i386 +libghc-type-level-dev_0.2.4-5_i386 +libghc-uniplate-dev_1.6.7-1+b2_i386 +libghc-unix-bytestring-dev_0.3.5-2+b1_i386 +libghc-unix-compat-dev_0.3.0.1-1+b1_i386 +libghc-unixutils-dev_1.50-1+b1_i386 +libghc-unlambda-dev_0.1-2+b2_i386 +libghc-unordered-containers-dev_0.2.1.0-1_i386 +libghc-uri-dev_0.1.6-1+b2_i386 +libghc-url-dev_2.1.2-4+b1_i386 +libghc-utf8-light-dev_0.4.0.1-2+b1_i386 +libghc-utf8-string-dev_0.3.7-1+b1_i386 +libghc-utility-ht-dev_0.0.5.1-3+b1_i386 +libghc-uuagc-cabal-dev_1.0.2.0-1+b1_i386 +libghc-uuid-dev_1.2.3-2+b4_i386 +libghc-uulib-dev_0.9.14-2_i386 +libghc-vault-dev_0.2.0.0-1+b2_i386 +libghc-vector-algorithms-dev_0.5.4-1+b2_i386 +libghc-vector-dev_0.9.1-2+b1_i386 +libghc-vector-space-dev_0.8.1-1_i386 +libghc-vector-space-points-dev_0.1.1.0-1+b1_i386 +libghc-void-dev_0.5.5.1-2+b1_i386 +libghc-vte-dev_0.12.1-1+b3_i386 +libghc-vty-dev_4.7.0.14-1+b1_i386 +libghc-wai-app-file-cgi-dev_0.5.8-1+b4_i386 +libghc-wai-app-static-dev_1.2.0.3-1+b3_i386 +libghc-wai-dev_1.2.0.2-1+b2_i386 +libghc-wai-extra-dev_1.2.0.4-1_i386 +libghc-wai-logger-dev_0.1.4-1+b6_i386 +libghc-wai-logger-prefork-dev_0.1.3-1+b6_i386 +libghc-wai-test-dev_1.2.0.2-1_i386 +libghc-warp-dev_1.2.1.1-1_i386 +libghc-warp-tls-dev_1.2.0.4-1+b4_i386 +libghc-web-routes-dev_0.25.3-2+b3_i386 +libghc-webkit-dev_0.12.3-2+b1_i386 +libghc-weighted-regexp-dev_0.3.1.1-2+b1_i386 +libghc-x11-dev_1.5.0.1-1+b2_i386 +libghc-x11-xft-dev_0.3.1-1+b3_i386 +libghc-xdg-basedir-dev_0.2.1-2+b1_i386 +libghc-xhtml-dev_3000.2.1-1_i386 +libghc-xml-conduit-dev_0.7.0.2-1_i386 +libghc-xml-dev_1.3.12-1+b2_i386 +libghc-xml-hamlet-dev_0.3.0.1-1~bpo70+1_i386 +libghc-xml-types-dev_0.3.1-2+b2_i386 +libghc-xml2html-dev_0.1.2.3-1_i386 +libghc-xmonad-contrib-dev_0.10-4~deb7u1_i386 +libghc-xmonad-dev_0.10-4+b2_i386 +libghc-xss-sanitize-dev_0.3.2-1+b1_i386 +libghc-yaml-dev_0.7.0.2-1+b2_i386 +libghc-yaml-light-dev_0.1.4-2+b2_i386 +libghc-yesod-auth-dev_1.0.2.1-2+b2_i386 +libghc-yesod-core-dev_1.0.1.2-1+b3_i386 +libghc-yesod-default-dev_1.0.1.1-1+b1_i386 +libghc-yesod-dev_1.0.1.6-2+b3_i386 +libghc-yesod-form-dev_1.0.0.4-1+b1_i386 +libghc-yesod-json-dev_1.0.0.1-1+b3_i386 +libghc-yesod-markdown-dev_0.4.0-1+b3_i386 +libghc-yesod-persistent-dev_1.0.0.1-1+b1_i386 +libghc-yesod-routes-dev_1.0.1.2-1_i386 +libghc-yesod-static-dev_1.0.0.2-1+b3_i386 +libghc-yesod-test-dev_0.2.0.6-1_i386 +libghc-zip-archive-dev_0.1.1.7-3+b2_i386 +libghc-zlib-bindings-dev_0.1.0.1-1_i386 +libghc-zlib-conduit-dev_0.4.0.1-1_i386 +libghc-zlib-dev_0.5.3.3-1+b1_i386 +libghc-zlib-enum-dev_0.2.2.1-1+b1_i386 +libghc6-agda-dev_1:8_all +libghc6-alut-dev_1:8_all +libghc6-arrows-dev_1:8_all +libghc6-binary-dev_1:8_all +libghc6-binary-shared-dev_1:8_all +libghc6-bzlib-dev_1:8_all +libghc6-cairo-dev_1:8_all +libghc6-cautious-file-dev_1:8_all +libghc6-cgi-dev_1:8_all +libghc6-colour-dev_1:8_all +libghc6-configfile-dev_1:8_all +libghc6-convertible-dev_1:8_all +libghc6-cpphs-dev_1:8_all +libghc6-criterion-dev_1:8_all +libghc6-csv-dev_1:8_all +libghc6-curl-dev_1:8_all +libghc6-data-accessor-dev_1:8_all +libghc6-dataenc-dev_1:8_all +libghc6-datetime-dev_1:8_all +libghc6-debian-dev_1:8_all +libghc6-deepseq-dev_1:8_all +libghc6-diagrams-dev_1:8_all +libghc6-diff-dev_1:8_all +libghc6-digest-dev_1:8_all +libghc6-edison-api-dev_1:8_all +libghc6-edison-core-dev_1:8_all +libghc6-editline-dev_1:8_all +libghc6-erf-dev_1:8_all +libghc6-event-list-dev_1:8_all +libghc6-explicit-exception-dev_1:8_all +libghc6-fastcgi-dev_1:8_all +libghc6-feed-dev_1:8_all +libghc6-fgl-dev_1:8_all +libghc6-filemanip-dev_1:8_all +libghc6-filestore-dev_1:8_all +libghc6-ftphs-dev_1:8_all +libghc6-gconf-dev_1:8_all +libghc6-ghc-events-dev_1:8_all +libghc6-ghc-mtl-dev_1:8_all +libghc6-ghc-paths-dev_1:8_all +libghc6-gio-dev_1:8_all +libghc6-gitit-dev_1:8_all +libghc6-glade-dev_1:8_all +libghc6-glfw-dev_1:8_all +libghc6-glib-dev_1:8_all +libghc6-glut-dev_1:8_all +libghc6-gstreamer-dev_1:8_all +libghc6-gtk-dev_1:8_all +libghc6-gtkglext-dev_1:8_all +libghc6-gtksourceview2-dev_1:8_all +libghc6-haddock-dev_1:8_all +libghc6-happstack-dev_1:8_all +libghc6-happstack-server-dev_1:8_all +libghc6-harp-dev_1:8_all +libghc6-hashed-storage-dev_1:8_all +libghc6-haskeline-dev_1:8_all +libghc6-haskell-lexer-dev_1:8_all +libghc6-haskell-src-dev_1:8_all +libghc6-haskelldb-dev_1:8_all +libghc6-haskelldb-hdbc-dev_1:8_all +libghc6-haskelldb-hdbc-odbc-dev_1:8_all +libghc6-haskelldb-hdbc-postgresql-dev_1:8_all +libghc6-haskelldb-hdbc-sqlite3-dev_1:8_all +libghc6-haskore-dev_1:8_all +libghc6-haxml-dev_1:8_all +libghc6-haxr-dev_1:8_all +libghc6-hdbc-dev_1:8_all +libghc6-hdbc-odbc-dev_1:8_all +libghc6-hdbc-postgresql-dev_1:8_all +libghc6-hdbc-sqlite3-dev_1:8_all +libghc6-highlighting-kate-dev_1:8_all +libghc6-hint-dev_1:8_all +libghc6-hjavascript-dev_1:8_all +libghc6-hjscript-dev_1:8_all +libghc6-hoauth-dev_1:8_all +libghc6-hscolour-dev_1:8_all +libghc6-hscurses-dev_1:8_all +libghc6-hsemail-dev_1:8_all +libghc6-hsh-dev_1:8_all +libghc6-hslogger-dev_1:8_all +libghc6-hsp-dev_1:8_all +libghc6-hsql-dev_1:8_all +libghc6-hsql-mysql-dev_1:8_all +libghc6-hsql-odbc-dev_1:8_all +libghc6-hsql-postgresql-dev_1:8_all +libghc6-hsql-sqlite3-dev_1:8_all +libghc6-hstringtemplate-dev_1:8_all +libghc6-hsx-dev_1:8_all +libghc6-html-dev_1:8_all +libghc6-http-dev_1:8_all +libghc6-hunit-dev_1:8_all +libghc6-hxt-dev_1:8_all +libghc6-ifelse-dev_1:8_all +libghc6-irc-dev_1:8_all +libghc6-json-dev_1:8_all +libghc6-language-c-dev_1:8_all +libghc6-lazysmallcheck-dev_1:8_all +libghc6-ldap-dev_1:8_all +libghc6-leksah-server-dev_1:8_all +libghc6-llvm-dev_1:8_all +libghc6-ltk-dev_1:8_all +libghc6-magic-dev_1:8_all +libghc6-markov-chain-dev_1:8_all +libghc6-maybet-dev_1:8_all +libghc6-midi-dev_1:8_all +libghc6-missingh-dev_1:8_all +libghc6-mmap-dev_1:8_all +libghc6-monadcatchio-mtl-dev_1:8_all +libghc6-monoid-transformer-dev_1:8_all +libghc6-mtl-dev_1:8_all +libghc6-mwc-random-dev_1:8_all +libghc6-network-dev_1:8_all +libghc6-non-negative-dev_1:8_all +libghc6-openal-dev_1:8_all +libghc6-opengl-dev_1:8_all +libghc6-pandoc-dev_1:8_all +libghc6-pango-dev_1:8_all +libghc6-parallel-dev_1:8_all +libghc6-parsec2-dev_1:8_all +libghc6-parsec3-dev_1:8_all +libghc6-pcre-light-dev_1:8_all +libghc6-polyparse-dev_1:8_all +libghc6-pretty-show-dev_1:8_all +libghc6-primitive-dev_1:8_all +libghc6-quickcheck1-dev_1:8_all +libghc6-quickcheck2-dev_1:8_all +libghc6-recaptcha-dev_1:8_all +libghc6-regex-base-dev_1:8_all +libghc6-regex-compat-dev_1:8_all +libghc6-regex-posix-dev_1:8_all +libghc6-regex-tdfa-dev_1:8_all +libghc6-regex-tdfa-utf8-dev_1:8_all +libghc6-safe-dev_1:8_all +libghc6-sdl-dev_1:8_all +libghc6-sdl-gfx-dev_1:8_all +libghc6-sdl-image-dev_1:8_all +libghc6-sdl-mixer-dev_1:8_all +libghc6-sdl-ttf-dev_1:8_all +libghc6-sendfile-dev_1:8_all +libghc6-sha-dev_1:8_all +libghc6-smtpclient-dev_1:8_all +libghc6-split-dev_1:8_all +libghc6-src-exts-dev_1:8_all +libghc6-statistics-dev_1:8_all +libghc6-stm-dev_1:8_all +libghc6-stream-dev_1:8_all +libghc6-strict-concurrency-dev_1:8_all +libghc6-svgcairo-dev_1:8_all +libghc6-syb-with-class-dev_1:8_all +libghc6-syb-with-class-instances-text-dev_1:8_all +libghc6-tagsoup-dev_1:8_all +libghc6-tar-dev_1:8_all +libghc6-terminfo-dev_1:8_all +libghc6-testpack-dev_1:8_all +libghc6-texmath-dev_1:8_all +libghc6-text-dev_1:8_all +libghc6-tokyocabinet-dev_1:8_all +libghc6-transformers-dev_1:8_all +libghc6-type-level-dev_1:8_all +libghc6-uniplate-dev_1:8_all +libghc6-unix-compat-dev_1:8_all +libghc6-unixutils-dev_1:8_all +libghc6-url-dev_1:8_all +libghc6-utility-ht-dev_1:8_all +libghc6-uulib-dev_1:8_all +libghc6-vector-algorithms-dev_1:8_all +libghc6-vector-dev_1:8_all +libghc6-vte-dev_1:8_all +libghc6-vty-dev_1:8_all +libghc6-webkit-dev_1:8_all +libghc6-x11-dev_1:8_all +libghc6-x11-xft-dev_1:8_all +libghc6-xhtml-dev_1:8_all +libghc6-xml-dev_1:8_all +libghc6-xmonad-contrib-dev_1:8_all +libghc6-xmonad-dev_1:8_all +libghc6-zip-archive-dev_1:8_all +libghc6-zlib-dev_1:8_all +libghemical-dev_3.0.0-2_i386 +libgif-dev_4.1.6-10_i386 +libgiftiio-dev_1.0.9-1_i386 +libgig-dev_3.3.0-2_i386 +libgii1-dev_1:1.0.2-4.1_i386 +libgimp2.0-dev_2.8.2-2+deb7u1_i386 +libginac-dev_1.6.2-1_i386 +libginspx-dev_20050529-3.1_i386 +libgio2.0-cil-dev_2.22.3-2_all +libgirara-dev_0.1.2-3_i386 +libgirepository1.0-dev_1.32.1-1_i386 +libgjs-dev_1.32.0-5_i386 +libgkeyfile-cil-dev_0.1-4_all +libgksu2-dev_2.0.13~pre1-6_i386 +libgl1-mesa-dev_8.0.5-4+deb7u2_i386 +libgl1-mesa-swx11-dev_8.0.5-4+deb7u2_i386 +libgl2ps-dev_1.3.6-1_i386 +libglade2-dev_1:2.6.4-1_i386 +libglade2.0-cil-dev_2.12.10-5_i386 +libglademm-2.4-dev_2.6.7-2_i386 +libgladeui-1-dev_3.6.7-2.1_i386 +libgladeui-dev_3.12.1-1_i386 +libglbsp-dev_2.24-1_i386 +libglc-dev_0.7.2-5+b1_i386 +libgle3-dev_3.1.0-7_i386 +libgles1-mesa-dev_8.0.5-4+deb7u2_i386 +libgles2-mesa-dev_8.0.5-4+deb7u2_i386 +libglew-dev_1.7.0-3_i386 +libglewmx-dev_1.7.0-3_i386 +libglfw-dev_2.7.2-1_i386 +libglib2.0-cil-dev_2.12.10-5_i386 +libglib2.0-dev_2.33.12+really2.32.4-5_i386 +libglibmm-2.4-dev_2.32.1-1_i386 +libglide2-dev_2002.04.10ds1-7_i386 +libglide3-dev_2002.04.10ds1-7_i386 +libglm-dev_0.9.3.3+dfsg-0.1_all +libglobus-authz-callout-error-dev_2.2-1_i386 +libglobus-authz-dev_2.2-1_i386 +libglobus-callout-dev_2.2-1_i386 +libglobus-common-dev_14.7-2_i386 +libglobus-ftp-client-dev_7.3-1_i386 +libglobus-ftp-control-dev_4.4-1_i386 +libglobus-gass-cache-dev_8.1-2_i386 +libglobus-gass-copy-dev_8.4-1_i386 +libglobus-gass-server-ez-dev_4.3-1_i386 +libglobus-gass-transfer-dev_7.2-1_i386 +libglobus-gfork-dev_3.2-1_i386 +libglobus-gram-client-dev_12.4-1_i386 +libglobus-gram-job-manager-callout-error-dev_2.1-2_i386 +libglobus-gram-protocol-dev_11.3-1_i386 +libglobus-gridftp-server-control-dev_2.5-2_i386 +libglobus-gridftp-server-dev_6.10-2_i386 +libglobus-gridmap-callout-error-dev_1.2-2_i386 +libglobus-gsi-callback-dev_4.2-1_i386 +libglobus-gsi-cert-utils-dev_8.3-1_i386 +libglobus-gsi-credential-dev_5.3-1_i386 +libglobus-gsi-openssl-error-dev_2.1-2_i386 +libglobus-gsi-proxy-core-dev_6.2-1_i386 +libglobus-gsi-proxy-ssl-dev_4.1-2_i386 +libglobus-gsi-sysconfig-dev_5.2-1_i386 +libglobus-gss-assist-dev_8.5-1_i386 +libglobus-gssapi-error-dev_4.1-2_i386 +libglobus-gssapi-gsi-dev_10.6-1_i386 +libglobus-io-dev_9.3-1_i386 +libglobus-openssl-module-dev_3.2-1_i386 +libglobus-rls-client-dev_5.2-8_i386 +libglobus-rsl-dev_9.1-2_i386 +libglobus-scheduler-event-generator-dev_4.6-1_i386 +libglobus-usage-dev_3.1-2_i386 +libglobus-xio-dev_3.3-1_i386 +libglobus-xio-gsi-driver-dev_2.3-1_i386 +libglobus-xio-pipe-driver-dev_2.2-1_i386 +libglobus-xio-popen-driver-dev_2.3-1_i386 +libgloox-dev_1.0-1.1_i386 +libgloox-dev_1.0.9-3~bpo70+1_i386 +libglpk-dev_4.45-1_i386 +libglrr-glib-dev_20050529-3.1_i386 +libglrr-gobject-dev_20050529-3.1_i386 +libglrr-gtk-dev_20050529-3.1_i386 +libglrr-widgets-dev_20050529-3.1_i386 +libglu1-mesa-dev_8.0.5-4+deb7u2_i386 +libglui-dev_2.36-4_i386 +libglw1-mesa-dev_8.0.0-1_i386 +libgme-dev_0.5.5-2_i386 +libgmerlin-avdec-dev_1.2.0~dfsg-1+b1_i386 +libgmerlin-dev_1.2.0~dfsg+1-1_i386 +libgmime-2.6-dev_2.6.10-1_i386 +libgmime2.6-cil-dev_2.6.10-1_all +libgmlib-dev_1.0.6-1_i386 +libgmm++-dev_4.1.1+dfsg1-11_all +libgmp-dev_2:5.0.5+dfsg-2_i386 +libgmp-ocaml-dev_20021123-17+b3_i386 +libgmp3-dev_2:5.0.5+dfsg-2_i386 +libgmpada3-dev_0.0.20120331-1_i386 +libgmsh-dev_2.7.0.dfsg-1~bpo70+1_i386 +libgmt-dev_4.5.7-2_i386 +libgmtk-dev_1.0.6-1_i386 +libgnadecommon2-dev_1.6.2-9_i386 +libgnadeodbc2-dev_1.6.2-9_i386 +libgnadesqlite3-2-dev_1.6.2-9_i386 +libgnatprj4.6-dev_4.6.3-8_i386 +libgnatvsn4.6-dev_4.6.3-8_i386 +libgnelib-dev_0.75+svn20091130-1+b1_i386 +libgnet-dev_2.0.8-2.2_i386 +libgnokii-dev_0.6.30+dfsg-1+b1_i386 +libgnome-bluetooth-dev_3.4.2-1_i386 +libgnome-desktop-3-dev_3.4.2-1_i386 +libgnome-desktop-dev_2.32.1-2_i386 +libgnome-keyring-dev_3.4.1-1_i386 +libgnome-keyring1.0-cil-dev_1.0.0-4_i386 +libgnome-mag-dev_1:0.16.3-1_i386 +libgnome-media-profiles-dev_3.0.0-1_i386 +libgnome-menu-3-dev_3.4.2-5_i386 +libgnome-menu-dev_3.0.1-4_i386 +libgnome-speech-dev_1:0.4.25-5_i386 +libgnome-vfs2.0-cil-dev_2.24.2-3_all +libgnome-vfsmm-2.6-dev_2.26.0-1_i386 +libgnome2-dev_2.32.1-3_i386 +libgnome2.0-cil-dev_2.24.2-3_i386 +libgnomeada2.24.1-dev_2.24.1-7_i386 +libgnomecanvas2-dev_2.30.3-1.2_i386 +libgnomecanvasmm-2.6-dev_2.26.0-1_i386 +libgnomecups1.0-dev_0.2.3-5_i386 +libgnomedesktop2.0-cil-dev_2.26.0-8_all +libgnomekbd-dev_3.4.0.2-1_i386 +libgnomemm-2.6-dev_2.30.0-1_i386 +libgnomeprint2.2-dev_2.18.8-3_i386 +libgnomeprintui2.2-dev_2.18.6-3_i386 +libgnomeui-dev_2.24.5-2_i386 +libgnomeuimm-2.6-dev_2.28.0-1_i386 +libgnomevfs2-dev_1:2.24.4-2_i386 +libgnuift0-dev_0.1.14-12_i386 +libgnuplot-ocaml-dev_0.8.3-3_i386 +libgnustep-base-dev_1.22.1-4_i386 +libgnustep-dl2-dev_0.12.0-9+nmu1_i386 +libgnustep-gui-dev_0.20.0-3_i386 +libgnutls-dev_2.12.20-8+deb7u1_i386 +libgnutls28-dev_3.2.15-2~bpo70+1_i386 +libgoa-1.0-dev_3.4.2-2_i386 +libgoffice-0.8-dev_0.8.17-1.2_i386 +libgofigure-dev_0.9.0-1+b2_i386 +libgoocanvas-dev_0.15-1_i386 +libgoocanvasmm-dev_0.15.4-1_i386 +libgoogle-perftools-dev_2.0-2_i386 +libgooglepinyin0-dev_0.1.2-1_i386 +libgpac-dev_0.5.0~dfsg0-1_i386 +libgpcl-dev_2.32-1_i386 +libgpds-dev_1.5.1-6_i386 +libgpelaunch-dev_0.14-6_i386 +libgpepimc-dev_0.9-4_i386 +libgpeschedule-dev_0.17-4_i386 +libgpevtype-dev_0.50-6_i386 +libgpewidget-dev_0.117-6_i386 +libgpg-error-dev_1.10-3.1_i386 +libgpg-error-dev_1.12-0.2~bpo70+1_i386 +libgpgme11-dev_1.2.0-1.4_i386 +libgpgme11-dev_1.4.3-0.1~bpo70+1_i386 +libgphoto2-2-dev_2.4.14-2_i386 +libgpiv3-dev_0.6.1-4_i386 +libgpm-dev_1.20.4-6_i386 +libgpod-cil-dev_0.8.2-7_i386 +libgpod-dev_0.8.2-7_i386 +libgpod-nogtk-dev_0.8.2-7_i386 +libgportugol-dev_1.1-2_i386 +libgps-dev_3.6-4+deb7u1_i386 +libgps-dev_3.9-3~bpo70+1_i386 +libgraflib1-dev_20061220+dfsg3-2_i386 +libgrafx11-1-dev_20061220+dfsg3-2_i386 +libgrantlee-dev_0.1.4-1_i386 +libgraphicsmagick++1-dev_1.3.16-1.1_i386 +libgraphicsmagick1-dev_1.3.16-1.1_i386 +libgraphite-dev_1:2.3.1-0.2_i386 +libgraphite2-dev_1.1.3-1_i386 +libgraphviz-dev_2.26.3-14+deb7u1_i386 +libgretl1-dev_1.9.9-1_i386 +libgrib-api-dev_1.9.16-2+b1_i386 +libgrib2c-dev_1.2.2-2+b1_i386 +libgridsite-dev_1.7.16-1_i386 +libgrilo-0.1-dev_0.1.19-1_i386 +libgringotts-dev_1.2.10~pre3-1_i386 +libgrits-dev_0.7-1_i386 +libgrok-dev_1.20110708.1-4_i386 +libgrss-dev_0.5.0-1_i386 +libgs-dev_9.05~dfsg-6.3+deb7u1_i386 +libgsasl7-dev_1.8.0-2_i386 +libgsecuredelete-dev_0.2-1_i386 +libgsf-1-dev_1.14.21-2.1_i386 +libgsf-gnome-1-dev_1.14.21-2.1_i386 +libgsl0-dev_1.15+dfsg.2-2_i386 +libgsm0710-dev_1.2.2-2_i386 +libgsm0710mux-dev_0.11.2-1.1_i386 +libgsm1-dev_1.0.13-4_i386 +libgsmme-dev_1.10-13.2_i386 +libgsnmp0-dev_0.3.0-1.1_i386 +libgsoap-dev_2.8.16-2~bpo70+1_i386 +libgsql-dev_0.2.2-1.2+b1_i386 +libgss-dev_1.0.2-1_i386 +libgssdp-1.0-dev_0.12.2.1-2_i386 +libgssglue-dev_0.4-2_i386 +libgst-dev_3.2.4-2_i386 +libgstbuzztard-dev_0.5.0-2+deb7u1_i386 +libgstreamer-ocaml-dev_0.1.0-3+b1_i386 +libgstreamer-plugins-bad0.10-dev_0.10.23-7.1+deb7u1_i386 +libgstreamer-plugins-bad1.0-dev_1.2.4-1~bpo70+1_i386 +libgstreamer-plugins-base0.10-dev_0.10.36-1.1_i386 +libgstreamer-plugins-base1.0-dev_1.2.4-1~bpo70+1_i386 +libgstreamer0.10-cil-dev_0.9.2-4_all +libgstreamer0.10-dev_0.10.36-1.2_i386 +libgstreamer1.0-dev_1.2.4-1~bpo70+1_i386 +libgstrtspserver-0.10-dev_0.10.8-3_i386 +libgtest-dev_1.6.0-2_i386 +libgtextutils-dev_0.6.2-1_i386 +libgtg-dev_0.2+dfsg-1_i386 +libgtk-3-dev_3.4.2-7_i386 +libgtk-sharp-beans2.0-cil-dev_2.14.1-3_all +libgtk-vnc-1.0-dev_0.5.0-3.1_i386 +libgtk-vnc-2.0-dev_0.5.0-3.1_i386 +libgtk2.0-cil-dev_2.12.10-5_i386 +libgtk2.0-dev_2.24.10-2_i386 +libgtkada2.24.1-dev_2.24.1-7_i386 +libgtkdatabox-0.9.1-1-dev_1:0.9.1.1-4_i386 +libgtkgl2.0-dev_2.0.1-2_i386 +libgtkglada2.24.1-dev_2.24.1-7_i386 +libgtkglarea-cil-dev_0.0.17-6_all +libgtkglext1-dev_1.2.0-2_i386 +libgtkglextmm-x11-1.2-dev_1.2.0-4.1_i386 +libgtkhex-3-dev_3.4.1-1_i386 +libgtkhotkey-dev_0.2.1-3_i386 +libgtkhtml-4.0-dev_4.4.4-1_i386 +libgtkhtml-editor-3.14-dev_3.32.2-2.1_i386 +libgtkhtml-editor-4.0-dev_4.4.4-1_i386 +libgtkhtml3.14-cil-dev_2.26.0-8_i386 +libgtkhtml3.14-dev_3.32.2-2.1_i386 +libgtkimageview-dev_1.6.4+dfsg-0.1_i386 +libgtkmathview-dev_0.8.0-8_i386 +libgtkmm-2.4-dev_1:2.24.2-1_i386 +libgtkmm-3.0-dev_3.4.2-1_i386 +libgtkpod-dev_2.1.2-1_i386 +libgtksourceview-3.0-dev_3.4.2-1_i386 +libgtksourceview2-cil-dev_2.26.0-8_i386 +libgtksourceview2.0-dev_2.10.4-1_i386 +libgtksourceviewmm-3.0-dev_3.2.0-1_i386 +libgtkspell-3-dev_3.0.0~hg20110814-1_i386 +libgtkspell-dev_2.0.16-1_i386 +libgtop2-dev_2.28.4-3_i386 +libgts-dev_0.7.6+darcs110121-1.1_i386 +libguac-dev_0.6.0-2_i386 +libgucharmap-2-90-dev_1:3.4.1.1-2.1_i386 +libgudev-1.0-dev_175-7.2_i386 +libgudev-1.0-dev_204-8~bpo70+1_i386 +libgudev1.0-cil-dev_0.1-3_all +libguess-dev_1.1-1_i386 +libguestfs-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-gobject-dev_1:1.18.1-1+deb7u3_i386 +libguestfs-ocaml-dev_1:1.18.1-1+deb7u3_i386 +libguichan-dev_0.8.2-10+b1_i386 +libgupnp-1.0-dev_0.18.4-1_i386 +libgupnp-av-1.0-dev_0.10.3-1_i386 +libgupnp-dlna-1.0-dev_0.6.6-1_i386 +libgupnp-igd-1.0-dev_0.2.1-2_i386 +libgusb-dev_0.1.3-5_i386 +libgutenprint-dev_5.2.9-1_i386 +libgutenprintui2-dev_5.2.9-1_i386 +libguytools2-dev_2.0.1-1.1_i386 +libgvnc-1.0-dev_0.5.0-3.1_i386 +libgweather-3-dev_3.4.1-1+build1_i386 +libgwenhywfar60-dev_4.12.0beta-1~bpo70+1_i386 +libgwenhywfar60-dev_4.3.3-1_i386 +libgwrap-runtime-dev_1.9.14-1.1_i386 +libgwyddion20-dev_2.28-2_i386 +libgxps-dev_0.2.2-2_i386 +libgyoto0-dev_0.0.3-5_i386 +libgyoto1-dev_0.1.0-2~bpo70+1_i386 +libh323plus-dev_1.24.0~dfsg2-1_i386 +libhackrf-dev_2014.04.1-1~bpo70+1_i386 +libhaildb-dev_2.3.2-1.2_i386 +libhal-dev_0.5.14-8_i386 +libhal-storage-dev_0.5.14-8_i386 +libhamlib++-dev_1.2.15.1-1_i386 +libhamlib-dev_1.2.15.1-1_i386 +libhandoff-dev_0.1-5_i386 +libhangul-dev_0.1.0-2_i386 +libharminv-dev_1.3.1-9_i386 +libhashkit-dev_1.0.8-1_i386 +libhavege-dev_1.9.1-1~bpo70+1_i386 +libhawknl-dev_1.6.8+dfsg2-1_i386 +libhbaapi-dev_2.2.5-1_i386 +libhbalinux-dev_1.0.14-1_i386 +libhd-dev_16.0-2.2_i386 +libhdate-dev_1.6-1_i386 +libhdf4-alt-dev_4.2r4-13_i386 +libhdf4-dev_4.2r4-13_i386 +libhdf4g-dev_4.2r4-13_all +libhdf5-dev_1.8.8-9_i386 +libhdf5-mpi-dev_1.8.8-9_i386 +libhdf5-mpich2-dev_1.8.8-9_i386 +libhdf5-openmpi-dev_1.8.8-9_i386 +libhdf5-serial-dev_1.8.8-9_i386 +libhdfeos-dev_2.17v1.00.dfsg.1-3_i386 +libhdhomerun-dev_20120405-1_i386 +libhe5-hdfeos-dev_5.1.13.dfsg.1-3_i386 +libheartbeat2-dev_1:3.0.5-3_i386 +libhepmc-dev_2.06.09-1_i386 +libhepmcfio-dev_2.06.09-1_i386 +libhepmcinterface8-dev_8.1.65-1_i386 +libherwig59-2-dev_20061220+dfsg3-2_i386 +libhesiod-dev_3.0.2-21_i386 +libhfsp-dev_1.0.4-12_i386 +libhighgui-dev_2.3.1-11_i386 +libhippocanvas-dev_0.3.1-1.1_i386 +libhiredis-dev_0.10.1-7_i386 +libhivex-dev_1.3.6-2_i386 +libhivex-dev_1.3.9-1~bpo70+1_i386 +libhivex-ocaml-dev_1.3.6-2_i386 +libhivex-ocaml-dev_1.3.9-1~bpo70+1_i386 +libhkl-dev_4.0.3-4_i386 +libhmsbeagle-dev_1.0-6_i386 +libhocr-dev_0.10.17-1+b2_i386 +libhpdf-dev_2.2.1-1_i386 +libhpmud-dev_3.12.6-3.1+deb7u1_i386 +libhsclient-dev_1.1.0-7-g1044a28-1_i386 +libhtmlcxx-dev_0.85-2_i386 +libhtp-dev_0.2.6-2_i386 +libhtsengine-dev_1.06-1_i386 +libhttp-ocaml-dev_0.1.5-1+b2_i386 +libhttrack-dev_3.46.1-1_i386 +libhunspell-dev_1.3.2-4_i386 +libhwloc-dev_1.4.1-4_i386 +libhx-dev_3.12.1-1_i386 +libhyantes-dev_1.3.0-1_i386 +libhyena-cil-dev_0.5-2_all +libhyphen-dev_2.8.3-2_i386 +libhypre-dev_2.8.0b-1_all +libhz-dev_0.3.16-3_i386 +libi2c-dev_3.1.0-2_all +libibcm-dev_1.0.4-1.1_i386 +libibcommon-dev_1.1.2-20090314-1_i386 +libibdm-dev_1.2-OFED-1.4.2-1.3_i386 +libibmad-dev_1.2.3-20090314-1.1_i386 +libibtk-dev_0.0.14-12_i386 +libibumad-dev_1.2.3-20090314-1.1_i386 +libibus-1.0-dev_1.4.1-9+deb7u1_i386 +libibus-1.0-dev_1.5.1.is.1.4.2-1~bpo70+1_i386 +libibus-qt-dev_1.3.1-2.1_i386 +libibverbs-dev_1.1.6-1_i386 +libical-dev_0.48-2_i386 +libicapapi-dev_1:0.1.6-1.1_i386 +libicc-dev_2.12+argyll1.4.0-8_i386 +libicc-utils-dev_1.6.4-1+b1_i386 +libice-dev_2:1.0.8-2_i386 +libicecc-dev_1.0.1-1~bpo70+1_i386 +libicee-dev_1.2.0-6.1_i386 +libicns-dev_0.8.1-1_i386 +libiconv-hook-dev_0.0.20021209-10_i386 +libics-dev_1.5.2-3_i386 +libicu-dev_4.8.1.1-12+deb7u1_i386 +libid3-3.8.3-dev_3.8.3-15_i386 +libid3tag0-dev_0.15.1b-10_i386 +libident-dev_0.22-3_i386 +libidl-dev_0.8.14-0.2_i386 +libidn11-dev_1.25-2_i386 +libidn2-0-dev_0.8-2_i386 +libido-0.1-dev_0.3.4-1_i386 +libido3-0.1-dev_0.3.4-1_i386 +libidzebra-2.0-dev_2.0.44-3_i386 +libiec16022-dev_0.2.4-1_i386 +libiec61883-dev_1.2.0-0.1_i386 +libieee1284-3-dev_0.2.11-10_i386 +libifp-dev_1.0.0.2-5_i386 +libifstat-dev_1.1-8_i386 +libigraph0-dev_0.5.4-2_i386 +libigstk4-dev_4.4.0-2+b1_i386 +libijs-dev_0.35-8_i386 +libiksemel-dev_1.2-4_i386 +libilmbase-dev_1.0.1-4_i386 +libimdi-dev_1.4.0-8_i386 +libiml-dev_1.0.3-4.2_i386 +libimlib2-dev_1.4.5-1_i386 +libimobiledevice-dev_1.1.1-4_i386 +libindi-dev_0.9.1-2_i386 +libindicate-dev_0.6.92-1_i386 +libindicate-gtk-dev_0.6.92-1_i386 +libindicate-gtk0.1-cil-dev_0.6.92-1_all +libindicate-gtk3-dev_0.6.92-1_i386 +libindicate-qt-dev_0.2.5.91-5_i386 +libindicate0.1-cil-dev_0.6.92-1_all +libindicator-dev_0.5.0-1_i386 +libindicator-messages-status-provider-dev_0.6.0-1_i386 +libindicator3-dev_0.5.0-1_i386 +libindigo-dev_1.0.0-2_i386 +libinfinity-0.5-dev_0.5.2-6.1_i386 +libini-config-dev_0.1.3-2_i386 +libinifiles-ocaml-dev_1.2-2_i386 +libinnodb-dev_1.0.6.6750-1_i386 +libinotify-ocaml-dev_1.0-1+b3_i386 +libinotifytools0-dev_3.14-1_i386 +libinput-pad-dev_1.0.1-2_i386 +libinsighttoolkit3-dev_3.20.1+git20120521-3_i386 +libinstpatch-dev_1.0.0-3_i386 +libint-dev_1.1.4-1_i386 +libiodbc2-dev_3.52.7-2+deb7u1_i386 +libion-dev_3.0.1~dfsg1-1_i386 +libipa-hbac-dev_1.8.4-2_i386 +libipathverbs-dev_1.2-1_i386 +libipe-dev_7.1.2-1_i386 +libipmiconsole-dev_1.1.5-3_i386 +libipmidetect-dev_1.1.5-3_i386 +libipmimonitoring-dev_1.1.5-3_i386 +libipset-dev_6.12.1-1_i386 +libiptcdata0-dev_1.0.4-3_i386 +libircclient-dev_1.3+dfsg1-3_i386 +libirman-dev_0.4.4-2_i386 +libirrlicht-dev_1.7.3+dfsg1-4_i386 +libisajet758-3-dev_20061220+dfsg3-2_i386 +libiscsi-dev_1.4.0-3_i386 +libisl-dev_0.10-3_i386 +libiso9660-dev_0.83-4_i386 +libisoburn-dev_1.2.2-2_i386 +libisofs-dev_1.2.2-1_i386 +libitl-dev_0.7.0-3_i386 +libitl-gobject-dev_0.2-1_i386 +libitpp-dev_4.2-4_i386 +libitsol-dev_1.0.0-2_i386 +libivykis-dev_0.30.1-2_i386 +libiw-dev_30~pre9-8_i386 +libjack-dev_1:0.121.3+20120418git75e3e20b-2.1_i386 +libjack-jackd2-dev_1.9.8~dfsg.4+20120529git007cdc37-5_i386 +libjalali-dev_0.4.0-1.1_i386 +libjama-dev_1.2.4-2_all +libjana-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-ecal-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjana-gtk-dev_0.0.0+git20091215.9ec1da8a-2+b4_i386 +libjansson-dev_2.3.1-2_i386 +libjasper-dev_1.900.1-13_i386 +libjaula-dev_1.4.0-3_i386 +libjavascriptcoregtk-1.0-dev_1.8.1-3.4_i386 +libjavascriptcoregtk-3.0-dev_1.8.1-3.4_i386 +libjbig-dev_2.0-2+deb7u1_i386 +libjbig2dec0-dev_0.11+20120125-1_i386 +libjconv-dev_2.8-6+b1_i386 +libjemalloc-dev_3.0.0-3_i386 +libjim-dev_0.73-3_i386 +libjpeg62-dev_6b1-3_i386 +libjpeg8-dev_8d-1_i386 +libjpgalleg4-dev_2:4.4.2-2.1_i386 +libjs-of-ocaml-dev_1.2-2_i386 +libjson-c-dev_0.11-3~bpo7+1_i386 +libjson-glib-dev_0.14.2-1_i386 +libjson-spirit-dev_4.04-1+b1_i386 +libjson-static-camlp4-dev_0.9.8-1+b5_i386 +libjson-wheel-ocaml-dev_1.0.6-2+b8_i386 +libjson0-dev_0.10-1.2_i386 +libjson0-dev_0.11-3~bpo7+1_i386 +libjsoncpp-dev_0.6.0~rc2-3_i386 +libjte-dev_1.19-1_i386 +libjthread-dev_1.3.1-3_i386 +libjudy-dev_1.0.5-1_i386 +libjuman-dev_5.1-2.1_i386 +libk3b-dev_2.0.2-6_i386 +libkactivities-dev_4:4.8.4-1_i386 +libkakasi2-dev_2.3.5~pre1+cvs20071101-1_i386 +libkal-dev_0.9.0-1_i386 +libkarma-cil-dev_0.1.2-2.3_all +libkarma-dev_0.1.2-2.3_i386 +libkate-dev_0.4.1-1_i386 +libkaya-gd-dev_0.4.4-6_i386 +libkaya-gl-dev_0.4.4-6_i386 +libkaya-mysql-dev_0.4.4-6_i386 +libkaya-ncurses-dev_0.4.4-6_i386 +libkaya-ncursesw-dev_0.4.4-6_i386 +libkaya-pgsql-dev_0.4.4-6_i386 +libkaya-sdl-dev_0.4.4-6_i386 +libkaya-sqlite3-dev_0.4.4-6_i386 +libkcddb-dev_4:4.8.4-2_i386 +libkdcraw-dev_4:4.8.4-1_i386 +libkdeedu-dev_4:4.8.4-1_i386 +libkdegames-dev_4:4.8.4-3_i386 +libkdtree++-dev_0.7.0-2_all +libkernlib1-dev_20061220+dfsg3-2_i386 +libkexiv2-dev_4:4.8.4-1_i386 +libkeybinder-dev_0.2.2-4_i386 +libkeyutils-dev_1.5.5-3_i386 +libkibi-dev_0.1-1_i386 +libkipi-dev_4:4.8.4-1_i386 +libkiten-dev_4:4.8.4-1_i386 +libklatexformula3-dev_3.2.6-1_i386 +libklibc-dev_2.0.1-3.1_i386 +libkmfl-dev_0.9.8-1_i386 +libkmflcomp-dev_0.9.8-1_i386 +libkml-dev_1.3.0~r863-4.1_i386 +libkmod-dev_9-3_i386 +libkokyu-dev_6.0.3+dfsg-0.1_i386 +libkonq5-dev_4:4.8.4-2_i386 +libkonqsidebarplugin-dev_4:4.8.4-2_i386 +libkopete-dev_4:4.8.4-1+b1_i386 +libkosd2-dev_0.8.1-1_i386 +libkpathsea-dev_2012.20120628-4_i386 +libkqueue-dev_1.0.4-2_i386 +libkrb5-dev_1.10.1+dfsg-5+deb7u1_i386 +libksane-dev_4:4.8.4-1_i386 +libksba-dev_1.2.0-2_i386 +libktoblzcheck1-dev_1.39-1_i386 +libktorrent-dev_1.2.1-1_i386 +libktpcommoninternalsprivate-dev_0.4.0-1_i386 +libkvutils-dev_2.9.0-1_i386 +libkvutils2.2-dev_2.9.0-1_all +libkwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libkwwidgets1-dev_1.0.0~cvs20100930-8_i386 +libkxl0-dev_1.1.7-16_i386 +liblablgl-ocaml-dev_1.04-5+b3_i386 +liblablgtk-extras-ocaml-dev_1.0-1+b2_i386 +liblablgtk2-gl-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-gnome-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtk2-ocaml-dev_2.14.2+dfsg-3_i386 +liblablgtkmathview-ocaml-dev_0.7.8-6+b1_i386 +liblablgtksourceview2-ocaml-dev_2.14.2+dfsg-3_i386 +libladr-dev_0.0.200902a-2.1_i386 +libladspa-ocaml-dev_0.1.4-1+b1_i386 +liblapack-dev_3.4.1+dfsg-1+deb70u1_i386 +liblapacke-dev_3.4.1+dfsg-1+deb70u1_i386 +liblas-dev_1.2.1-5+b1_i386 +liblash-compat-dev_1+dfsg0-3_all +liblasi-dev_1.1.0-1_i386 +liblasso3-dev_2.3.6-2_i386 +liblastfm-dev_0.4.0~git20090710-2_i386 +liblastfm-ocaml-dev_0.3.0-2+b6_i386 +liblcgdm-dev_1.8.2-1+b2_i386 +liblcms1-dev_1.19.dfsg-1.2_i386 +liblcms2-dev_2.2+git20110628-2.2+deb7u1_i386 +libldap-ocaml-dev_2.1.8-8+b9_i386 +libldap2-dev_2.4.31-1+nmu2_i386 +libldb-dev_1:1.1.16-1~bpo70+1_i386 +libldb-dev_1:1.1.6-1_i386 +libldns-dev_1.6.13-1_i386 +libldns-dev_1.6.16-1~bpo70+1_i386 +libledit-ocaml-dev_2.03-1+b2_i386 +liblensfun-dev_0.2.5-2_i386 +libleptonica-dev_1.69-3.1_i386 +libleveldb-dev_0+20120530.gitdd0d562-1_i386 +liblfc-dev_1.8.2-1+b2_i386 +liblhapdf-dev_5.8.7+repack-1_i386 +liblhasa-dev_0.0.7-2_i386 +liblicense-dev_0.8.1-3_i386 +libliggghts-dev_3.0.0+repack-1~bpo70+1_i386 +liblightdm-gobject-dev_1.2.2-4_i386 +liblightdm-qt-dev_1.2.2-4_i386 +liblilv-dev_0.14.2~dfsg0-4_i386 +liblinear-dev_1.8+dfsg-1_i386 +liblinebreak2-dev_2.1-1_i386 +liblink-grammar4-dev_4.7.4-2_i386 +liblinphone-dev_3.5.2-10_i386 +liblip-dev_2.0.0-1.1_i386 +liblircclient-dev_0.9.0~pre1-1_i386 +liblistaller-glib-dev_0.5.5-2_i386 +liblivemedia-dev_2012.05.17-1_i386 +liblldpctl-dev_0.7.9-1~bpo70+1_i386 +libllvm-2.9-ocaml-dev_2.9+dfsg-7_i386 +libllvm-3.0-ocaml-dev_3.0-10_i386 +libllvm-3.1-ocaml-dev_3.1-1_i386 +libllvm-ocaml-dev_1:3.0-14+nmu2_i386 +liblo-dev_0.26~repack-7_i386 +liblo-ocaml-dev_0.1.0-1+b1_i386 +liblo10k1-dev_1.0.25-2_i386 +libloadpng4-dev_2:4.4.2-2.1_i386 +liblockdev1-dev_1.0.3-1.5_i386 +liblockfile-dev_1.09-5_i386 +liblodo3.0-dev_3.0.2+dfsg-4+b1_i386 +liblog4ada2-dev_1.2-3_i386 +liblog4c-dev_1.2.1-3_i386 +liblog4cplus-dev_1.0.4-1_i386 +liblog4cpp5-dev_1.0-4_i386 +liblog4cxx10-dev_0.10.0-1.2_i386 +liblog4net-cil-dev_1.2.10+dfsg-6_all +liblog4shib-dev_1.0.4-1_i386 +liblog4tango4-dev_7.2.6+dfsg-14_i386 +liblog4tango5-dev_8.1.2c+dfsg-4~bpo70+1_i386 +liblogforwarderutils2-dev_2.7-1_i386 +liblogging-stdlog-dev_1.0.4-1~bpo70+1_i386 +liblognorm-dev_0.3.4-1_i386 +liblogservicecomponentbase2-dev_2.7-1_i386 +liblogservicetoolbase2-dev_2.7-1_i386 +liblogsys-dev_1.4.2-3_i386 +liblogthread-dev_3.0.12-3.2+deb7u2_i386 +libloki-dev_0.1.7-3_i386 +libloudmouth1-dev_1.4.3-9_i386 +liblouis-dev_2.4.1-1_i386 +liblouisutdml-dev_2.2.0-1_i386 +liblouisxml-dev_2.4.0-3_i386 +liblowpan-dev_0.2.2-2.1_all +liblpsolve55-dev_5.5.0.13-7_i386 +liblqr-1-0-dev_0.4.1-2_i386 +liblrdf0-dev_0.4.0-5_i386 +liblrm2-dev_1.0.9+hg2665-1_i386 +liblrs-dev_0.42c-1+b1_i386 +liblscp-dev_0.5.6-6_i386 +libltcsmpte-dev_0.4.4-1_i386 +libltdl-dev_2.4.2-1.1_i386 +liblttctl-dev_0.89-05122011-1_i386 +liblttd-dev_0.89-05122011-1_i386 +liblttng-ust-dev_2.0.4-1_i386 +liblttoolbox3-3.1-0-dev_3.1.0-1.1_i386 +liblttvtraceread-2.6-dev_0.12.38-21032011-1+b1_i386 +liblua5.1-0-dev_5.1.5-4_i386 +liblua5.1-apr-dev_0.23.2-1_all +liblua5.1-bitop-dev_1.0.2-1_all +liblua5.1-cgi-dev_5.1.4+dfsg-2_all +liblua5.1-copas-dev_1.1.6-5_all +liblua5.1-curl-dev_0.3.0-7_all +liblua5.1-cyrussasl-dev_1.0.0-4_all +liblua5.1-event-dev_0.4.1-2_all +liblua5.1-expat-dev_1.2.0-5+deb7u1_all +liblua5.1-filesystem-dev_1.5.0+16+g84f1af5-1_all +liblua5.1-leg-dev_0.1.2-8_all +liblua5.1-logging-dev_1.2.0-1_all +liblua5.1-lpeg-dev_0.10.2-5_all +liblua5.1-md5-dev_1.1.2-6_all +liblua5.1-oocairo-dev_1.4-1.2_i386 +liblua5.1-oopango-dev_1.1-1_i386 +liblua5.1-orbit-dev_2.2.0+dfsg1-1_all +liblua5.1-posix-dev_5.1.19-2_all +liblua5.1-rex-onig-dev_2.6.0-2_all +liblua5.1-rex-pcre-dev_2.6.0-2_all +liblua5.1-rex-posix-dev_2.6.0-2_all +liblua5.1-rings-dev_1.2.3-1_all +liblua5.1-rrd-dev_1.4.7-2_i386 +liblua5.1-sec-dev_0.4.1-1_all +liblua5.1-soap-dev_3.0-3_all +liblua5.1-socket-dev_2.0.2-8_all +liblua5.1-sql-mysql-dev_2.3.0-1+build0_all +liblua5.1-sql-postgres-dev_2.3.0-1+build0_all +liblua5.1-sql-sqlite3-dev_2.3.0-1+build0_all +liblua5.1-svn-dev_0.4.0-7_all +liblua5.1-wsapi-fcgi-dev_1.5-3_all +liblua5.1-xmlrpc-dev_1.2.1-5_all +liblua5.1-zip-dev_1.2.3-11_all +liblua5.2-dev_5.2.1-3_i386 +liblua50-dev_5.0.3-6_i386 +libluabind-dev_0.9.1+dfsg-5_i386 +liblualib50-dev_5.0.3-6_i386 +liblunar-1-dev_2.0.1-2.2_i386 +liblunar-date-dev_2.4.0-1_i386 +liblv2dynparam1-dev_2-5_i386 +liblvm2-dev_2.02.95-8_i386 +liblwipv6-dev_1.5a-2_i386 +liblwt-glib-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ocaml-dev_2.3.2-1+b3_i386 +liblwt-ssl-ocaml-dev_2.3.2-1+b3_i386 +liblz-dev_1.3-2_i386 +liblz4-dev_0.0~r114-2~bpo70+1_i386 +liblzma-dev_5.1.1alpha+20120614-2_i386 +liblzo2-dev_2.06-1_i386 +libm17n-dev_1.6.3-2_i386 +libm17n-im-config-dev_0.9.0-3_i386 +libm4ri-dev_0.0.20080521-2_i386 +libmaa-dev_1.3.1-1_i386 +libmad-ocaml-dev_0.4.4-1+b1_i386 +libmad0-dev_0.15.1b-7_i386 +libmadlib-dev_1.3.0-2.1_i386 +libmagic-dev_1:5.17-1~bpo70+1_i386 +libmagic-dev_5.11-2+deb7u3_i386 +libmagic-ocaml-dev_0.7.3-5+b3_i386 +libmagick++-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickcore-dev_8:6.7.7.10-5+deb7u3_i386 +libmagickwand-dev_8:6.7.7.10-5+deb7u3_i386 +libmagics++-dev_2.14.11-4_i386 +libmailutils-dev_1:2.99.97-3_i386 +libmalaga-dev_7.12-4_i386 +libmaloc-dev_0.2-2.3_i386 +libmapi-dev_1:1.0-3_i386 +libmapi-dev_1:2.1-1~bpo70+1_i386 +libmapiadmin-dev_1:1.0-3_i386 +libmapiadmin-dev_1:2.1-1~bpo70+1_i386 +libmapipp-dev_1:1.0-3_i386 +libmapipp-dev_1:2.1-1~bpo70+1_i386 +libmapiproxy-dev_1:1.0-3_i386 +libmapiproxy-dev_1:2.1-1~bpo70+1_i386 +libmapistore-dev_1:1.0-3_i386 +libmapistore-dev_1:2.1-1~bpo70+1_i386 +libmapnik-dev_2.0.0+ds1-3_all +libmapnik2-dev_2.0.0+ds1-3+b4_i386 +libmarble-dev_4:4.8.4-3_i386 +libmarco-dev_1.8.0+dfsg1-5~bpo70+1_i386 +libmarkdown2-dev_2.1.3-3_i386 +libmatchbox-dev_1.9-osso8-3_i386 +libmate-desktop-dev_1.8.1+dfsg1-1~bpo70+1_i386 +libmate-menu-dev_1.8.0-2~bpo70+1_i386 +libmate-panel-applet-dev_1.8.0+dfsg1-2~bpo70+1_i386 +libmate-sensors-applet-plugin-dev_1.8.0+dfsg1-1~bpo70+1_i386 +libmate-slab-dev_1.8.1+dfsg1-3~bpo70+1_i386 +libmate-window-settings-dev_1.8.1+dfsg1-3~bpo70+1_i386 +libmatedict-dev_1.8.0+dfsg1-4~bpo70+1_i386 +libmatekbd-dev_1.8.0-2~bpo70+1_i386 +libmatepolkit-dev_1.8.0+dfsg1-3~bpo70+1_i386 +libmateweather-dev_1.8.0-2~bpo70+1_i386 +libmath++-dev_0.0.4-4_i386 +libmatheval-dev_1.1.8-1_i386 +libmathlib2-dev_20061220+dfsg3-2_i386 +libmatio-dev_1.3.4-4_i386 +libmatrixssl1.8-dev_1.8.8-1_i386 +libmatroska-dev_1.3.0-2_i386 +libmbt0-dev_3.2.8-1_i386 +libmcpp-dev_2.7.2-1.1_i386 +libmcrypt-dev_2.5.8-3.1_i386 +libmcs-dev_0.7.2-2.1_i386 +libmd3-dev_0.1.92-4_i386 +libmdc2-dev_0.10.7-1+b2_i386 +libmdds-dev_0.5.4-1_all +libmdsp-dev_0.11-10_i386 +libmeanwhile-dev_1.0.2-4_i386 +libmecab-dev_0.99.3-3_i386 +libmed-dev_3.0.3-3_i386 +libmedc-dev_3.0.3-3_i386 +libmediainfo-dev_0.7.58-1_i386 +libmediastreamer-dev_3.5.2-10_i386 +libmedimport-dev_3.0.3-3_i386 +libmeep-dev_1.1.1-8+deb7u1_i386 +libmeep-lam4-dev_1.1.1-10~deb7u1_i386 +libmeep-mpi-default-dev_1.1.1-10~deb7u1_i386 +libmeep-mpich2-dev_1.1.1-10~deb7u1_i386 +libmeep-openmpi-dev_1.1.1-9~deb7u2_i386 +libmelt-ocaml-dev_1.4.0-1_i386 +libmemcache-dev_1.4.0.rc2-1_i386 +libmemcached-dev_1.0.8-1_i386 +libmemphis-0.2-dev_0.2.3-2_i386 +libmenhir-ocaml-dev_20120123.dfsg-1_i386 +libmenu-cache1-dev_0.3.3-1_i386 +libmercator-0.3-dev_0.3.0-2_i386 +libmeschach-dev_1.2b-13_i386 +libmetacity-dev_1:2.34.3-4_i386 +libmgl-dev_1.11.2-17_i386 +libmhash-dev_0.9.9.9-1.1_i386 +libmicrohttpd-dev_0.9.20-1+deb7u1_i386 +libmigemo-dev_20110227-7_i386 +libmikmatch-ocaml-dev_1.0.4-1+b1_i386 +libmikmod2-dev_3.1.12-5_i386 +libmilter-dev_8.14.4-4_i386 +libmimedir-dev_0.5.1-4_i386 +libmimedir-gnome-dev_0.4.2-5_i386 +libmimelib1-dev_5:1.1.4-2_i386 +libmimetic-dev_0.9.7-3_i386 +libmimic-dev_1.0.4-2.1_i386 +libminc-dev_2.1.10-1+b1_i386 +libming-dev_1:0.4.4-1.1_i386 +libmini18n-dev_0.2.1-1_i386 +libminidjvu-dev_0.8.svn.2010.05.06+dfsg-0.2_i386 +libminiupnpc-dev_1.5-2_i386 +libminiupnpc-dev_1.6-3~bpo70+1_i386 +libmirisdr-dev_0.0.4.59ba37-2~bpo70+1_i386 +libmission-control-plugins-dev_1:5.12.3-1_i386 +libmkv-dev_0.6.5.1-1_i386 +libmlpcap-ocaml-dev_0.9-16_i386 +libmlpost-ocaml-dev_0.8.1-3_i386 +libmlt++-dev_0.8.0-4_i386 +libmlt-dev_0.8.0-4_i386 +libmlx4-dev_1.0.4-1_i386 +libmm-dev_1.4.2-4_i386 +libmm-ocaml-dev_0.2.0-1+b1_i386 +libmmpong0.9-dev_0.9.1-2.1_i386 +libmms-dev_0.6.2-3_i386 +libmng-dev_1.0.10-3_i386 +libmnl-dev_1.0.3-3_i386 +libmodbus-dev_3.0.3-1_i386 +libmodglue1-dev_1.17-2.1_all +libmodplug-dev_1:0.8.8.4-3+deb7u1+git20130828_all +libmoe-dev_1.5.8-1_i386 +libmongo-client-dev_0.1.5-1+deb7u1_i386 +libmono-2.0-dev_2.10.8.1-8_i386 +libmono-addins-cil-dev_0.6.2-2_all +libmono-addins-gui-cil-dev_0.6.2-2_all +libmono-addins-msbuild-cil-dev_0.6.2-2_all +libmono-cecil-cil-dev_0.9.5+dfsg-2_all +libmono-cecil-flowanalysis-cil-dev_0.1~vcs20110809.r1.b34edf6-2_all +libmono-cil-dev_2.10.8.1-8_all +libmono-reflection-cil-dev_1.0+git20110407+d2343843-2_all +libmono-uia-cil-dev_2.1-4_all +libmono-upnp-cil-dev_0.1.2-1_all +libmono-zeroconf-cil-dev_0.9.0-4_all +libmonogame-cil-dev_2.5.1+dfsg-3_all +libmopac7-dev_1.15-5_i386 +libmorph-dev_1:20090926_i386 +libmosquitto0-dev_0.15-2_all +libmosquittopp0-dev_0.15-2_all +libmotif-dev_2.3.3-8_i386 +libmount-dev_2.20.1-5.3_i386 +libmowgli-dev_1.0.0-1_i386 +libmozjs-dev_24.4.0esr-1~deb7u2_i386 +libmozjs185-dev_1.8.5-1.0.0+dfsg-4_i386 +libmp3lame-dev_3.99.5+repack1-3_i386 +libmp3lame-ocaml-dev_0.3.1-1+b1_i386 +libmp3splt-dev_0.7.2-2_i386 +libmp4v2-dev_2.0.0~dfsg0-1_i386 +libmpc-dev_0.9-4_i386 +libmpcdec-dev_2:0.1~r459-4_i386 +libmpd-dev_0.20.0-1.1_i386 +libmpdclient-dev_2.3-1_i386 +libmpeg2-4-dev_0.4.1-3_i386 +libmpeg3-dev_1.5.4-5_i386 +libmpfi-dev_1.5.1-1_i386 +libmpfr-dev_3.1.0-5_i386 +libmpg123-dev_1.14.4-1_i386 +libmpich2-dev_1.4.1-4.2_i386 +libmpikmeans-dev_1.5-1+b1_i386 +libmrml1-dev_0.1.14-12_i386 +libmrmpi-dev_1.0~20110620.dfsg-2_i386 +libmrss0-dev_0.19.2-3_i386 +libmsgpack-dev_0.5.7-2_i386 +libmsn-dev_4.2-2_i386 +libmspub-dev_0.0.6-1~bpo70+1_i386 +libmsv-dev_0.0.0-1_i386 +libmsv-dev_1.0-1~bpo70+1_i386 +libmtbl-dev_0.2-1_i386 +libmtcp-dev_1.2.5-1_i386 +libmtdev-dev_1.1.2-1_i386 +libmthca-dev_1.0.6-1_i386 +libmtp-dev_1.1.3-35-g0ece104-5_i386 +libmtp-dev_1.1.6-20-g1b9f164-1~bpo70+1_i386 +libmudflap0-4.4-dev_4.4.7-2_i386 +libmudflap0-4.6-dev_4.6.3-14_i386 +libmudflap0-4.7-dev_4.7.2-5_i386 +libmulticobex1-dev_0.23-1.1_i386 +libmumps-dev_4.10.0.dfsg-3_i386 +libmumps-ptscotch-dev_4.10.0.dfsg-3_i386 +libmumps-scotch-dev_4.10.0.dfsg-3_i386 +libmumps-seq-dev_4.10.0.dfsg-3_i386 +libmunge-dev_0.5.10-1_i386 +libmuparser-dev_2.1.0-3_i386 +libmupdf-dev_0.9-2_i386 +libmupen64plus-dev_1.99.5-6_all +libmuroar-dev_0.1.8-2_i386 +libmusic-dev_1.0.7-1.2_i386 +libmusicbrainz3-dev_3.0.2-2.1_i386 +libmusicbrainz5-dev_5.0.1-2_i386 +libmutter-dev_3.4.1-5_i386 +libmx-dev_1.4.6-1_i386 +libmxml-dev_2.6-2_i386 +libmyproxy-dev_5.6-1_i386 +libmysql++-dev_3.1.0-2+b1_i386 +libmysql-cil-dev_6.4.3-2_all +libmysql-ocaml-dev_1.1.1-1_i386 +libmysqlclient-dev_5.5.35+dfsg-0+wheezy1_i386 +libmysqlcppconn-dev_1.1.0-4+b1_i386 +libmysqld-dev_5.5.35+dfsg-0+wheezy1_i386 +libmythes-dev_2:1.2.2-1_i386 +libnabrit-dev_0.4.1-1_i386 +libnacl-dev_20110221-4_i386 +libnacore-dev_0.4.0-3_i386 +libnanohttp-dev_1.1.0-17.1_i386 +libnatpmp-dev_20110808-3_i386 +libnautilus-extension-dev_3.4.2-1+build1_i386 +libnauty-dev_2.4r2-1_i386 +libnbio-dev_0.30-1_i386 +libncap-dev_1.9.2-1+b2_i386 +libncbi6-dev_6.1.20120620-2_i386 +libncp-dev_2.2.6-9_i386 +libncurses5-dev_5.9-10_i386 +libncursesada2-dev_5.9.20110404-7_i386 +libncursesw5-dev_5.9-10_i386 +libndesk-dbus-glib1.0-cil-dev_0.4.1-4_all +libndesk-dbus1.0-cil-dev_0.6.0-6_all +libndr-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libndr-standard-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libnecpp-dev_1.5.0+cvs20101003-2.1_i386 +libneon27-dev_0.29.6-3_i386 +libneon27-gnutls-dev_0.29.6-3_i386 +libnes-dev_1.1.3-1_i386 +libnet1-dev_1.1.4-2.1_i386 +libnet6-1.3-dev_1:1.3.14-1_i386 +libnetcdf-dev_1:4.1.3-6+b1_i386 +libnetcf-dev_0.1.9-2_i386 +libnetclasses-dev_1.06.dfsg-5+b3_i386 +libnetfilter-conntrack-dev_1.0.1-1_i386 +libnetfilter-cttimeout-dev_1.0.0-1_i386 +libnetfilter-log-dev_1.0.0-1_i386 +libnetfilter-queue-dev_0.0.17-1_i386 +libnethttpd-ocaml-dev_3.5.1-1_i386 +libnetpbm10-dev_2:10.0-15+b1_i386 +libnetpbm9-dev_2:10.0-15+b1_i386 +libnetsvcs-dev_6.0.3+dfsg-0.1_i386 +libnewlib-dev_1.18.0-6.2_i386 +libnewmat10-dev_1.10.4-5_i386 +libnewt-dev_0.52.14-11.1_i386 +libnewtonsoft-json-cil-dev_4.5r6-1_all +libnexus0-dev_4.2.1-svn1614-1+b2_i386 +libnfnetlink-dev_1.0.0-1.1_i386 +libnfo-dev_1.0.1-1_i386 +libnfs-dev_1.3.0-2_i386 +libnfsidmap-dev_0.25-4_i386 +libnice-dev_0.1.2-1_i386 +libnids-dev_1.23-2_i386 +libnifti-dev_2.0.0-1_i386 +libnih-dbus-dev_1.0.3-4.1_i386 +libnih-dev_1.0.3-4.1_i386 +libnini-cil-dev_1.1.0+dfsg.2-4_all +libnjb-dev_2.2.7~dfsg0-3_i386 +libnl-3-dev_3.2.7-4_i386 +libnl-cli-3-dev_3.2.7-4_i386 +libnl-dev_1.1-7_i386 +libnl-genl-3-dev_3.2.7-4_i386 +libnl-nf-3-dev_3.2.7-4_i386 +libnl-route-3-dev_3.2.7-4_i386 +libnm-glib-dev_0.9.4.0-10_i386 +libnm-glib-vpn-dev_0.9.4.0-10_i386 +libnm-gtk-dev_0.9.4.1-5_i386 +libnm-util-dev_0.9.4.0-10_i386 +libnmz7-dev_2.0.21-6_i386 +libnoise-dev_1.0.0+nmu1_i386 +libnotify-cil-dev_0.4.0~r3032-6_all +libnotify-dev_0.7.5-1_i386 +libnotmuch-dev_0.13.2-1_i386 +libnotmuch-dev_0.16-1~bpo70+1_i386 +libnova-dev_0.14.0-2_i386 +libnpth0-dev_0.90-2_i386 +libnsbmp0-dev_0.0.1-1.1_i386 +libnsgif0-dev_0.0.1-1.1_i386 +libnspr4-dev_2:4.9.2-1+deb7u1_i386 +libnss3-dev_2:3.14.5-1_i386 +libntdb-dev_1.0-2~bpo70+1_i386 +libntfs-dev_2.0.0-1+b1_i386 +libntl-dev_5.5.2-2_i386 +libntlm0-dev_1.2-1_i386 +libntrack-dev_016-1.1_i386 +libntrack-glib-dev_016-1.1_i386 +libntrack-gobject-dev_016-1.1_i386 +libntrack-qt4-dev_016-1.1_i386 +libnuclient-dev_2.4.3-2.2_i386 +libnuma-dev_2.0.8~rc4-1_i386 +libnunit-cil-dev_2.6.0.12051+dfsg-2_all +libnussl-dev_2.4.3-2.2_i386 +libnvtt-dev_2.0.8-1+dfsg-2_i386 +libnvtt-dev_2.0.8-1+dfsg-4~bpo70+1_i386 +libnxcl-dev_0.9-3.1_i386 +libnxml0-dev_0.18.3-4_i386 +libnzb-dev_0.0.20050629-6.1_i386 +liboasis-ocaml-dev_0.2.0-6_i386 +liboasis3-dev_3.3.beta.dfsg.1-8+b1_i386 +liboath-dev_1.12.4-1_i386 +liboauth-dev_0.9.4-3.1_i386 +libobby-0.4-dev_0.4.8-1_i386 +libobexftp0-dev_0.23-1.1_i386 +libobrowser-ocaml-dev_1.1.1+dfsg-1+b9_i386 +libobus-ocaml-dev_1.1.3-1+b8_i386 +libocamlbricks-ocaml-dev_0.50.1-4+b5_i386 +libocamlbricks-ocaml-dev_0.90+bzr367-1~bpo70+1_i386 +libocamlgraph-ocaml-dev_1.8.2-2_i386 +libocamlgraph-viewer-ocaml-dev_1.8.2-2_i386 +libocamlgsl-ocaml-dev_0.6.0-7+b2_i386 +libocamlnet-gtk2-ocaml-dev_3.5.1-1_i386 +libocamlnet-ocaml-dev_3.5.1-1_i386 +libocamlnet-ssl-ocaml-dev_3.5.1-1_i386 +libocamlodbc-ocaml-dev_2.15-5+b3_i386 +libocamlviz-ocaml-dev_1.01-2+b2_i386 +libocas-dev_0.93-1_i386 +liboce-foundation-dev_0.9.1-3_i386 +liboce-modeling-dev_0.9.1-3_all +liboce-ocaf-dev_0.9.1-3_all +liboce-ocaf-lite-dev_0.9.1-3_all +liboce-visualization-dev_0.9.1-3_all +libocpf-dev_1:1.0-3_i386 +libocpf-dev_1:2.1-1~bpo70+1_i386 +libocrad-dev_0.22~rc1-2_i386 +libocsigen-ocaml-dev_1.3.4-2+b12_i386 +libocsigen-xhtml-ocaml-dev_1.3.4-2+b12_i386 +libocsigenserver-ocaml-dev_2.1-1_i386 +libocsync-dev_0.91.4-1~bpo70+1_i386 +liboctave-dev_3.6.2-5+deb7u1_i386 +liboctave-dev_3.8.1-1~bpo70+1_i386 +libode-dev_2:0.11.1-4_i386 +libode-sp-dev_2:0.11.1-4_i386 +libodin-dev_1.8.5-2_i386 +libodn-ocaml-dev_0.0.8-1_i386 +libofa0-dev_0.9.3-5_i386 +libofapi-dev_0git20070620-6_i386 +libofdt-dev_1.3.6-1_i386 +libofetion-dev_2.2.2-1_i386 +libofx-dev_1:0.9.4-2.1_i386 +libogdi3.2-dev_3.2.0~beta2-7_i386 +libogg-dev_1.3.0-4_i386 +libogg-ocaml-dev_0.4.3-1+b1_i386 +liboggkate-dev_0.4.1-1_i386 +liboggplay1-dev_0.2.1~git20091227-1.2_i386 +liboggz2-dev_1.1.1-1_i386 +liboglappth-dev_1.0.0-2_i386 +libogre-1.8-dev_1.8.0+dfsg1-3_i386 +libogre-dev_1.7.4+dfsg1-7_i386 +liboil0.3-dev_0.3.17-2_i386 +libois-dev_1.3.0+dfsg0-5_i386 +libomhacks-dev_0.16-1_i386 +libomnievents-dev_1:2.6.2-2_i386 +libomniorb4-dev_4.1.6-2_i386 +libomnithread3-dev_4.1.6-2_i386 +libomxil-bellagio-dev_0.9.3-1+b1_i386 +libonig-dev_5.9.1-1_i386 +liboobs-1-dev_3.0.0-1_i386 +liboop-dev_1.0-9_i386 +libooptools-dev_2.7-1_i386 +libopal-dev_3.10.4~dfsg-3_i386 +libopenafs-dev_1.6.1-3+deb7u2_i386 +libopenafs-dev_1.6.9-1~bpo70+1_i386 +libopenais-dev_1.1.4-4.1_i386 +libopenal-dev_1:1.14-4_i386 +libopenbabel-dev_2.3.1+dfsg-4_i386 +libopenblas-dev_0.1.1-6+deb7u3_i386 +libopencc-dev_0.3.0-3_i386 +libopenconnect-dev_3.20-4_i386 +libopencore-amrnb-dev_0.1.3-2_i386 +libopencore-amrwb-dev_0.1.3-2_i386 +libopencryptoki-dev_2.3.1+dfsg-3_i386 +libopencsg-dev_1.3.2-2_i386 +libopenct1-dev_0.6.20-1.2_i386 +libopencv-calib3d-dev_2.3.1-11_i386 +libopencv-contrib-dev_2.3.1-11_i386 +libopencv-core-dev_2.3.1-11_i386 +libopencv-dev_2.3.1-11_i386 +libopencv-features2d-dev_2.3.1-11_i386 +libopencv-flann-dev_2.3.1-11_i386 +libopencv-gpu-dev_2.3.1-11_i386 +libopencv-highgui-dev_2.3.1-11_i386 +libopencv-imgproc-dev_2.3.1-11_i386 +libopencv-legacy-dev_2.3.1-11_i386 +libopencv-ml-dev_2.3.1-11_i386 +libopencv-objdetect-dev_2.3.1-11_i386 +libopencv-video-dev_2.3.1-11_i386 +libopendbx1-dev_1.4.6-5~bpo70+1_i386 +libopendkim-dev_2.6.8-4_i386 +libopendmarc-dev_1.2.0+dfsg-1~bpo70+1_i386 +libopenexr-dev_1.6.1-6_i386 +libopenhpi-dev_2.14.1-1.2_i386 +libopenigtlink1-dev_1.9.2~svn7468-1_i386 +libopenimageio-dev_1.0.5+dfsg0-1_i386 +libopenipmi-dev_2.0.16-1.3_i386 +libopenjpeg-dev_1.3+dfsg-4.7_i386 +libopenmeeg-dev_2.0.0.dfsg-5_i386 +libopenmpi-dev_1.4.5-1_i386 +libopenobex1-dev_1.5-2_i386 +libopenr2-dev_1.3.2-1.1_i386 +libopenraw-dev_0.0.9-3+b1_i386 +libopenrawgnome-dev_0.0.9-3+b1_i386 +libopenrpt-dev_3.3.4-6~bpo70+1_i386 +libopenscap-dev_0.8.0-4+b1_i386 +libopenscenegraph-dev_3.0.1-4_i386 +libopenslide-dev_3.2.6-2_i386 +libopensm2-dev_3.2.6-20090317-2.1_i386 +libopenthreads-dev_3.0.1-4_i386 +libopentk-cil-dev_1.0.20101006+dfsg1-1_all +libopentoken3-dev_4.0b-3_i386 +libopenturns-dev_1.0-4_i386 +libopenusb-dev_1.1.0-2_i386 +libopenvg1-mesa-dev_8.0.5-4+deb7u2_i386 +libopenvrml-dev_0.18.9-5+deb7u1_i386 +libopenwalnut1-dev_1.2.5-1.1+b1_i386 +liboping-dev_1.6.2-1_i386 +libopkele-dev_2.0.4-5.3_i386 +libopts25-dev_1:5.12-0.1_i386 +libopus-dev_0.9.14+20120615-1+nmu1_i386 +libopus-dev_1.1-1~bpo70+1_i386 +liborange-dev_0.4-2_i386 +liborbit2-dev_1:2.14.19-0.1_i386 +liborc-0.4-dev_1:0.4.16-2_i386 +liborc-0.4-dev_1:0.4.19-1~bpo70+1_i386 +liborigin-dev_20080225-2.1_i386 +liborigin2-dev_2:20110117-1+b2_i386 +libortp-dev_3.5.2-10_i386 +liboscpack-dev_1.0.2-1_i386 +libosgearth-dev_2.0+dfsg-4+b3_i386 +libosinfo-1.0-dev_0.1.1-1_i386 +libosip2-dev_3.6.0-4_i386 +libosl-dev_0.5.0-1_i386 +libosmesa6-dev_8.0.5-4+deb7u2_i386 +libosmgpsmap-dev_0.7.3-3_i386 +libosmium-dev_0.0~20111213-g7f3500a-3+b2_i386 +libosmosdr-dev_0.1.8.effcaa7-1~bpo70+1_i386 +libosmpbf-dev_1.2.1-3_i386 +libosp-dev_1.5.2-10_i386 +libosptk3-dev_3.4.2-1+b1_i386 +libossim-dev_1.7.21-4_i386 +libossp-sa-dev_1.2.6-1_i386 +libossp-uuid-dev_1.6.2-1.3_i386 +libostyle-dev_1.4devel1-20.1+b1_i386 +libotcl1-dev_1.14+dfsg-2_i386 +libotf-dev_0.9.12-2_i386 +libotf-trace-dev_1.10.2+dfsg-2_i386 +libotpw-dev_1.3-2_i386 +libotr2-dev_3.2.1-1+deb7u1_i386 +libotr5-dev_4.0.0-2.2~bpo70+1_i386 +libots-dev_0.5.0-2.1_i386 +libounit-ocaml-dev_1.1.1-1_i386 +libow-dev_2.8p15-1_i386 +libowfat-dev_0.28-6_i386 +libowfat-dietlibc-dev_0.28-6_i386 +libownet-dev_2.8p15-1_i386 +libp11-dev_0.2.8-2_i386 +libp11-kit-dev_0.12-3_i386 +libp11-kit-dev_0.20.2-1~bpo70+1_i386 +libpackagekit-glib2-dev_0.7.6-3_i386 +libpackagekit-qt2-dev_0.7.6-3_i386 +libpacketdump3-dev_3.0.14-1_i386 +libpacklib-lesstif1-dev_20061220+dfsg3-2_i386 +libpacklib1-dev_20061220+dfsg3-2_i386 +libpacparser-dev_1.3.0-2_i386 +libpam-ocaml-dev_1.1-4+b3_i386 +libpam0g-dev_1.1.3-7.1_i386 +libpanel-applet-4-dev_3.4.2.1-4_i386 +libpango1.0-dev_1.30.0-1_i386 +libpangomm-1.4-dev_2.28.4-1_i386 +libpano13-dev_2.9.18+dfsg-5_i386 +libpantomime1.2-dev_1.2.0~pre3+snap20071004+dfsg-4+b1_i386 +libpaper-dev_1.1.24+nmu2_i386 +libpaps-dev_0.6.8-6_i386 +libpaq-dev_1.0.4-3+b1_i386 +libpar2-0-dev_0.2.1-1_i386 +libpari-dev_2.5.1-2_i386 +libpari-dev_2.7.1-1~bpo70+1_i386 +libparmetis-dev_3.1.1-4_i386 +libparpack2-dev_3.1.1-2.1_i386 +libparrot-dev_4.0.0-3_i386 +libparser++-dev_0.2.3-2_all +libparted0-dev_2.3-12_i386 +libpasswdqc-dev_1.2.0-1_all +libpath-utils-dev_0.1.3-2_i386 +libpathfinder-dev_1.1.3-0.4+b1_i386 +libpawlib-lesstif3-dev_1:2.14.04.dfsg.2-8_i386 +libpawlib2-dev_1:2.14.04.dfsg.2-8_i386 +libpcap-dev_1.3.0-1_all +libpcap0.8-dev_1.3.0-1_i386 +libpcapnav0-dev_0.8-1_i386 +libpci-dev_1:3.1.9-6_i386 +libpciaccess-dev_0.13.1-2_i386 +libpcl1-dev_1.6-1_i386 +libpcre++-dev_0.9.5-5.1_i386 +libpcre-ocaml-dev_6.2.5-1_i386 +libpcre3-dev_1:8.30-5_i386 +libpcscada2-dev_0.7.1-4_i386 +libpcsclite-dev_1.8.4-1+deb7u1_i386 +libpdflib804-2-dev_20061220+dfsg3-2_i386 +libpe-rules2-dev_1.1.7-1_i386 +libpe-status3-dev_1.1.7-1_i386 +libpeas-dev_1.4.0-2_i386 +libpengine3-dev_1.1.7-1_i386 +libperl-dev_5.14.2-21+deb7u1_i386 +libperl4caml-ocaml-dev_0.9.5-4+b4_i386 +libpetsc3.2-dev_3.2.dfsg-6_i386 +libpfqueue-dev_0.5.6-8_i386 +libpfs-dev_1.8.5-1_i386 +libpgm-dev_5.1.118-1~dfsg-0.1_i386 +libpgocaml-ocaml-dev_1.5-2_i386 +libpgpool-dev_3.1.3-5_i386 +libpgtcl-dev_1:1.5-6_i386 +libphash0-dev_0.9.4-1.2_i386 +libphat-dev_0.4.1-5_i386 +libphobos-4.4-dev_1.063-4.4.7-1_i386 +libphobos2-4.6-dev_0.29.1-4.6.3-2_i386 +libphone-ui-dev_1:0.0.1+git20110825-3_i386 +libphone-utils-dev_0.1+git20110523-2.1_i386 +libphonon-dev_4:4.6.0.0-3_i386 +libphononexperimental-dev_4:4.6.0.0-3_i386 +libphotos202-dev_20061220+dfsg3-2_i386 +libphtools2-dev_20061220+dfsg3-2_i386 +libphysfs-dev_2.0.2-6_i386 +libpiano-dev_2012.05.06-2_i386 +libpigment0.3-dev_0.3.17-1_i386 +libpils2-dev_1.0.9+hg2665-1_i386 +libpinyin0-dev_0.6.91-1_i386 +libpion-common-dev_4.0.7+dfsg-3.1_i386 +libpion-net-dev_4.0.7+dfsg-3.1_i386 +libpipeline-dev_1.2.1-1_i386 +libpisock-dev_0.12.5-5_i386 +libpixman-1-dev_0.26.0-4+deb7u1_i386 +libpjproject-dev_2.1.0.0.ast20130823-1~bpo70+1_i386 +libpkcs11-helper1-dev_1.09-1_i386 +libplayer-dev_2.0.1-2.1_i386 +libplayerc++3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerc3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercommon3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayercore3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerdrivers3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerinterface3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerjpeg3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayertcp3.0-dev_3.0.2+dfsg-4+b1_i386 +libplayerwkb3.0-dev_3.0.2+dfsg-4+b1_i386 +libplib-dev_1.8.5-6_i386 +libplist++-dev_1.8-1_i386 +libplist-dev_1.8-1_i386 +libpload-dev_1.4.2-3_i386 +libplot-dev_2.6-3_i386 +libploticus0-dev_2.41-5_i386 +libplotmm-dev_0.1.2-2_i386 +libplplot-ada0-dev_5.9.9-5_i386 +libplplot-dev_5.9.9-5_i386 +libplumb2-dev_1.0.9+hg2665-1_i386 +libplumbgpl2-dev_1.0.9+hg2665-1_i386 +libpmap3.0-dev_3.0.2+dfsg-4+b1_i386 +libpmi0-dev_2.3.4-2+b1_i386 +libpmount-dev_0.0.16_i386 +libpng++-dev_0.2.5-1_all +libpng12-dev_1.2.49-1_i386 +libpnglite-dev_0.1.17-1_i386 +libpoco-dev_1.3.6p1-4_i386 +libpodofo-dev_0.9.0-1.1+b1_i386 +libpoker-eval-dev_138.0-1_i386 +libpolarssl-dev_1.2.9-1~deb7u2_i386 +libpoldiff-dev_3.3.7-3_i386 +libpolkit-agent-1-dev_0.105-3_i386 +libpolkit-backend-1-dev_0.105-3_i386 +libpolkit-gobject-1-dev_0.105-3_i386 +libpolkit-qt-1-dev_0.103.0-1_i386 +libpolybori-dev_0.5~rc1-2.2_i386 +libpolylib64-dev_5.22.5-3+dfsg_i386 +libpolyml-dev_5.2.1-1.1_i386 +libpolyorb2-dev_2.8~20110207-5.1_i386 +libpomp-dev_1.1+dfsg-2_i386 +libpoppler-cil-dev_0.0.3-2_all +libpoppler-cpp-dev_0.18.4-6_i386 +libpoppler-cpp-dev_0.24.5-4~bpo70+1_i386 +libpoppler-dev_0.18.4-6_i386 +libpoppler-dev_0.24.5-4~bpo70+1_i386 +libpoppler-glib-dev_0.18.4-6_i386 +libpoppler-glib-dev_0.24.5-4~bpo70+1_i386 +libpoppler-private-dev_0.18.4-6_i386 +libpoppler-private-dev_0.24.5-4~bpo70+1_i386 +libpoppler-qt4-dev_0.18.4-6_i386 +libpoppler-qt4-dev_0.24.5-4~bpo70+1_i386 +libpopplerkit-dev_0.0.20051227svn-7+b1_i386 +libpopt-dev_1.16-7_i386 +libportaudio-dev_18.1-7.1_i386 +libportaudio-ocaml-dev_0.2.0-1+b1_i386 +libportmidi-dev_1:184-2.1_i386 +libportsmf-dev_0.1~svn20101010-3_i386 +libpostgresql-ocaml-dev_1.18.0-1_i386 +libpostproc-dev_6:0.8.10-1_i386 +libpostproc-dev_6:0.git20120821-4~bpo70+1_i386 +libpotrace-dev_1.10-1_i386 +libpowerman0-dev_2.3.5-1_i386 +libppd-dev_2:0.10-7.1_i386 +libppl0.11-dev_0.11.2-8_i386 +libpq-dev_9.1.13-0wheezy1_i386 +libpqxx3-dev_3.1-1.1_i386 +libprelude-dev_1.0.0-9_i386 +libpreludedb-dev_1.0.0-1.1+b2_i386 +libpresage-dev_0.8.8-1_i386 +libpri-dev_1.4.12-2_i386 +libprinterconf-dev_0.5-12_i386 +libprintsys-dev_0.6-13_i386 +libprison-dev_1.0+dfsg-1_i386 +libprocps0-dev_1:3.3.3-3_i386 +libproj-dev_4.7.0-2_i386 +libprojectm-dev_2.1.0+dfsg-1_i386 +libprojectm-qt-dev_2.1.0+dfsg-1_i386 +libprotobuf-c0-dev_0.14-1+b1_i386 +libprotobuf-dev_2.4.1-3_i386 +libprotoc-dev_2.4.1-3_i386 +libproxy-dev_0.3.1-6_i386 +libproxychains-dev_3.1-3_i386 +libpspell-dev_0.60.7~20110707-1_i386 +libpst-dev_0.6.54-4.1_i386 +libpstoedit-dev_3.60-2+b1_i386 +libpstreams-dev_0.7.0-2_all +libpt-dev_2.10.4~dfsg-1_i386 +libptexenc-dev_2012.20120628-4_i386 +libpth-dev_2.0.7-16_i386 +libpthread-stubs0-dev_0.3-3_i386 +libpthread-workqueue-dev_0.8.2-1_i386 +libptscotch-dev_5.1.12b.dfsg-1.2_i386 +libpugl-dev_0~svn32+dfsg0-1_i386 +libpulse-dev_2.0-6.1_i386 +libpulse-dev_4.0-6~bpo7+1_i386 +libpulse-ocaml-dev_0.1.2-1+b1_i386 +libpuma-dev_1:1.1+svn20120529-2_i386 +libpurelibc-dev_0.4.1-1_i386 +libpurple-dev_2.10.7-2~bpo70+1_all +libpurple-dev_2.10.9-1~deb7u1_all +libpuzzle-dev_0.9-5_i386 +libpwl-dev_0.11.2-8_i386 +libpxp-ocaml-dev_1.2.2-1+b4_i386 +libpycaml-ocaml-dev_0.82-14+b2_i386 +libpyside-dev_1.1.1-3_i386 +libpythia8-dev_8.1.65-1_i386 +libpythonqt2-dev_2.0.1-1.1_i386 +libqalculate-dev_0.9.7-8_i386 +libqapt-dev_1.3.0-2_i386 +libqb-dev_0.11.1-2_i386 +libqca2-dev_2.0.3-4_i386 +libqd-dev_2.3.11.dfsg-2.1_i386 +libqdaccolib-dev_0.8.2-1_i386 +libqdbm++-dev_1.8.78-2_i386 +libqdbm-dev_1.8.78-2_i386 +libqdjango-dev_0.2.5-2_i386 +libqedje-dev_0.4.0+lgpl-3_i386 +libqfits-dev_6.2.0-5_i386 +libqgis-dev_1.7.4+1.7.5~20120320-1.1+b1_i386 +libqglviewer-qt4-dev_2.3.4-4.2_i386 +libqgpsmm-dev_3.6-4+deb7u1_i386 +libqgpsmm-dev_3.9-3~bpo70+1_i386 +libqhull-dev_2009.1-3_i386 +libqimageblitz-dev_1:0.0.6-4_i386 +libqjson-dev_0.7.1-7_i386 +libqmf-dev_0.16-6+deb7u1_i386 +libqmf2-dev_0.16-6+deb7u1_i386 +libqmfconsole2-dev_0.16-6+deb7u1_i386 +libqmfengine1-dev_0.16-6+deb7u1_i386 +libqmmp-dev_0.5.5-1+b1_i386 +libqmmpui-dev_0.5.5-1+b1_i386 +libqoauth-dev_1.0.1-1_i386 +libqof-dev_0.8.6-1_i386 +libqofexpensesobjects-dev_0.1.9-2_i386 +libqpdf-dev_2.3.1-4_i386 +libqpidbroker2-dev_0.16-6+deb7u1_i386 +libqpidclient2-dev_0.16-6+deb7u1_i386 +libqpidcommon2-dev_0.16-6+deb7u1_i386 +libqpidmessaging2-dev_0.16-6+deb7u1_i386 +libqpidtypes1-dev_0.16-6+deb7u1_i386 +libqpol-dev_3.3.7-3_i386 +libqpx-dev_0.7.1.002-5_i386 +libqpx-dev_0.7.2-4~bpo70+1_i386 +libqrencode-dev_3.3.0-2_i386 +libqrupdate-dev_1.1.1-1_i386 +libqsastime-dev_5.9.9-5_i386 +libqscintilla2-dev_2.6.2-2_all +libqt4-dev_4:4.8.2+dfsg-11_i386 +libqt4-opengl-dev_4:4.8.2+dfsg-11_i386 +libqt4-private-dev_4:4.8.2+dfsg-11_i386 +libqt4pas-dev_2.5-6_i386 +libqtassistantclient-dev_4.6.3-4_i386 +libqtexengine-dev_0.3-3_i386 +libqtgstreamer-dev_0.10.2-2_i386 +libqtruby4shared-dev_4:4.8.4-1_i386 +libqtwebkit-dev_2.2.1-5_i386 +libquantlib0-dev_1.2-2+b1_i386 +libquantum-dev_1.1.0-3_i386 +libquicktime-dev_2:1.2.4-3_i386 +libquorum-dev_1.4.2-3_i386 +libquvi-dev_0.4.1-1_i386 +libqwt-dev_6.0.0-1.2_i386 +libqwt5-qt4-dev_5.2.2-3_i386 +libqwtplot3d-qt4-dev_0.2.7+svn191-7_i386 +libqxmlrpc-dev_0.0.svn6-2_i386 +libqxmpp-dev_0.4.92-1_i386 +libqxmpp-dev_0.8.0-1~bpo70+1_i386 +libqxt-dev_0.6.1-6_i386 +libqzeitgeist-dev_0.7.0-1+b1_i386 +libqzion-dev_0.4.0+lgpl-4_i386 +librabbitmq-dev_0.0.1.hg216-1_i386 +libradare2-dev_0.9-3_i386 +libradius1-dev_0.3.2-14_i386 +libradiusclient-ng-dev_0.5.6-1.1_i386 +librados-dev_0.80.1-1~bpo70+1_i386 +libranlip-dev_1.0-4.1_i386 +librapi2-dev_0.15-2.1_i386 +libraptor1-dev_1.4.21-7.1_i386 +libraptor2-dev_2.0.8-2_i386 +librarian-dev_0.8.1-5_i386 +librasqal3-dev_0.9.29-1_i386 +librasterlite-dev_1.1~svn11-2_i386 +libraul-dev_0.8.0+dfsg0-0.1+b1_i386 +libraw-dev_0.14.6-2_i386 +libraw1394-dev_2.0.9-1_i386 +librbd-dev_0.80.1-1~bpo70+1_i386 +librcc-dev_0.2.9-3_i386 +librcd-dev_0.1.13-3_i386 +librdf0-dev_1.0.15-1+b1_i386 +librdkit-dev_201203-3_i386 +librdmacm-dev_1.0.15-1+deb7u1_i386 +librdmacm-dev_1.0.16-1~bpo70+1_i386 +librdmawrap2-dev_0.16-6+deb7u1_i386 +libreact-ocaml-dev_0.9.3-1_i386 +libreadline-dev_6.2+dfsg-0.1_i386 +libreadline-gplv2-dev_5.2+dfsg-2~deb7u1_i386 +libreadline6-dev_6.2+dfsg-0.1_i386 +librec-dev_1.5-1_i386 +librecode-dev_3.6-20_i386 +librecon-1.9-dev_1.9.7-1~bpo70+1_i386 +libref-array-dev_0.1.3-2_i386 +libregina3-dev_3.6-2_i386 +libregistry-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libreins-ocaml-dev_0.1a-4+b1_i386 +libreiser4-dev_1.0.7-6.3_i386 +librelp-dev_1.0.0-1_i386 +librelp-dev_1.2.7-1~bpo70+1_i386 +libremctl-dev_3.2-4_i386 +libremctl-dev_3.8-1~bpo70+1_i386 +librenaissance0-dev_0.9.0-4+b3_i386 +libreoffice-dev_1:3.5.4+dfsg2-0+deb7u2_i386 +libreoffice-dev_1:4.2.5-1~bpo70+1_i386 +librep-dev_0.90.2-1.3_i386 +libreplaygain-dev_1.0~r475-1_i386 +libres-ocaml-dev_3.2.0-2+b3_i386 +libresample1-dev_0.1.3-4_i386 +libresid-builder-dev_2.1.1-14_i386 +libresiprocate-1.8-dev_1.8.5-4_i386 +libresiprocate-1.9-dev_1.9.7-1~bpo70+1_i386 +libresiprocate-turn-client-1.8-dev_1.8.5-4_i386 +libresiprocate-turn-client-1.9-dev_1.9.7-1~bpo70+1_i386 +librest-dev_0.7.12-3_i386 +librest-extras-dev_0.7.12-3_i386 +librgxg-dev_0.1-1~bpo70+1_i386 +librhash-cil-dev_1.2.9-8+deb7u1_all +librhash-dev_1.2.9-8+deb7u1_i386 +librheolef-dev_6.1-2.1_i386 +librivet-dev_1.8.0-1_i386 +librlog-dev_1.4-2_i386 +libroar-dev_1.0~beta2-3_i386 +libroot-bindings-python-dev_5.34.00-2_i386 +libroot-bindings-ruby-dev_5.34.00-2_i386 +libroot-core-dev_5.34.00-2_i386 +libroot-geom-dev_5.34.00-2_i386 +libroot-graf2d-gpad-dev_5.34.00-2_i386 +libroot-graf2d-graf-dev_5.34.00-2_i386 +libroot-graf2d-postscript-dev_5.34.00-2_i386 +libroot-graf3d-eve-dev_5.34.00-2_i386 +libroot-graf3d-g3d-dev_5.34.00-2_i386 +libroot-graf3d-gl-dev_5.34.00-2_i386 +libroot-gui-dev_5.34.00-2_i386 +libroot-gui-ged-dev_5.34.00-2_i386 +libroot-hist-dev_5.34.00-2_i386 +libroot-hist-spectrum-dev_5.34.00-2_i386 +libroot-html-dev_5.34.00-2_i386 +libroot-io-dev_5.34.00-2_i386 +libroot-io-xmlparser-dev_5.34.00-2_i386 +libroot-math-foam-dev_5.34.00-2_i386 +libroot-math-genvector-dev_5.34.00-2_i386 +libroot-math-mathcore-dev_5.34.00-2_i386 +libroot-math-mathmore-dev_5.34.00-2_i386 +libroot-math-matrix-dev_5.34.00-2_i386 +libroot-math-minuit-dev_5.34.00-2_i386 +libroot-math-mlp-dev_5.34.00-2_i386 +libroot-math-physics-dev_5.34.00-2_i386 +libroot-math-quadp-dev_5.34.00-2_i386 +libroot-math-smatrix-dev_5.34.00-2_i386 +libroot-math-splot-dev_5.34.00-2_i386 +libroot-math-unuran-dev_5.34.00-2_i386 +libroot-misc-memstat-dev_5.34.00-2_i386 +libroot-misc-minicern-dev_5.34.00-2_i386 +libroot-misc-table-dev_5.34.00-2_i386 +libroot-montecarlo-eg-dev_5.34.00-2_i386 +libroot-montecarlo-vmc-dev_5.34.00-2_i386 +libroot-net-auth-dev_5.34.00-2_i386 +libroot-net-bonjour-dev_5.34.00-2_i386 +libroot-net-dev_5.34.00-2_i386 +libroot-net-ldap-dev_5.34.00-2_i386 +libroot-proof-clarens-dev_5.34.00-2_i386 +libroot-proof-dev_5.34.00-2_i386 +libroot-proof-proofplayer-dev_5.34.00-2_i386 +libroot-roofit-dev_5.34.00-2_i386 +libroot-tmva-dev_5.34.00-2_i386 +libroot-tree-dev_5.34.00-2_i386 +libroot-tree-treeplayer-dev_5.34.00-2_i386 +librostlab-blast0-dev_1.0.0-2_i386 +librostlab3-dev_1.0.20-1_i386 +librpcsecgss-dev_0.19-5_i386 +librplay3-dev_3.3.2-14_i386 +librpm-dev_4.10.0-5+deb7u1_i386 +librra-dev_0.14-1.2_i386 +librrd-dev_1.4.7-2_i386 +librsl-dev_1.42-2_i386 +librsskit-dev_0.3-2_i386 +librsvg2-2.0-cil-dev_2.26.0-8_all +librsvg2-dev_2.36.1-2_i386 +librsync-dev_0.9.7-9_i386 +librtai-dev_3.8.1-4_i386 +librtas-dev_1.3.6-1_i386 +librtasevent-dev_1.3.6-1_i386 +librtaudio-dev_4.0.10~ds0-2_i386 +librtfcomp-dev_1.1-5+b1_i386 +librtfilter-dev_1.1-4_i386 +librtlsdr-dev_0.5.3-3~bpo70+1_i386 +librtmidi-dev_1.0.15~ds0-2_i386 +librtmp-dev_2.4+20111222.git4e06e21-1_i386 +librubberband-dev_1.3-1.3_i386 +librudecgi-dev_5.0.0-1_i386 +libruli4-dev_0.33-1.1_i386 +librxp-dev_1.5.0-1_i386 +libs3-dev_2.0-1_i386 +libs3d-dev_0.2.2-8_i386 +libs3dw-dev_0.2.2-8_i386 +libsaamf3-dev_1.1.4-4.1_i386 +libsackpt3-dev_1.1.4-4.1_i386 +libsaclm3-dev_1.1.4-4.1_i386 +libsaevt3-dev_1.1.4-4.1_i386 +libsage-dev_0.2.0-4.1_i386 +libsalck3-dev_1.1.4-4.1_i386 +libsam-dev_1.4.2-3_i386 +libsamba-credentials-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-hostconfig-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-policy-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamba-util-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsamdb-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsaml2-dev_2.4.3-4_i386 +libsaml2-dev_2.5.3-2~bpo70+1_i386 +libsampleicc-dev_1.6.4-1+b1_i386 +libsamplerate-ocaml-dev_0.1.1-1+b3_i386 +libsamplerate0-dev_0.1.8-5_i386 +libsamsg4-dev_1.1.4-4.1_i386 +libsane-dev_1.0.22-7.4_i386 +libsane-extras-dev_1.0.22.2_i386 +libsanlock-dev_2.2-2_i386 +libsary-dev_1:1.2.0-2.1_i386 +libsasl2-dev_2.1.25.dfsg1-6+deb7u1_i386 +libsatmr3-dev_1.1.4-4.1_i386 +libsbc-dev_1.1-2~bpo7+1_i386 +libsbjson-dev_2.3.2-2_i386 +libsbsms-dev_2.0.1-1_i386 +libsbuf-dev_9.0+ds1-4_i386 +libsbuild-dev_1.6.4-4_i386 +libsc-dev_2.3.1-14_i386 +libscalapack-mpi-dev_1.8.0-9_i386 +libscalapack-pvm-dev_1.8.0-9_i386 +libscalc-dev_0.2.4-1_i386 +libscamperfile0-dev_20111202b-1_i386 +libschroedinger-dev_1.0.11-2_i386 +libschroedinger-ocaml-dev_0.1.0-1+b3_i386 +libscim-dev_1.4.13-5_i386 +libscm-dev_5e5-3.2_i386 +libscotch-dev_5.1.12b.dfsg-1.2_i386 +libscotchmetis-dev_5.1.12b.dfsg-1.2_i386 +libscotchparmetis-dev_5.1.12b.dfsg-1.2_i386 +libsctp-dev_1.0.11+dfsg-2_i386 +libscythestat-dev_1.0.2-1_all +libsdl-console-dev_2.1-3_i386 +libsdl-gfx1.2-dev_2.0.23-3_i386 +libsdl-image1.2-dev_1.2.12-2_i386 +libsdl-mixer1.2-dev_1.2.12-3_i386 +libsdl-net1.2-dev_1.2.8-2_i386 +libsdl-ocaml-dev_0.9.0-1_i386 +libsdl-pango-dev_0.1.2-6_i386 +libsdl-sge-dev_030809dfsg-3_i386 +libsdl-sound1.2-dev_1.0.3-6_i386 +libsdl-stretch-dev_0.3.1-3_i386 +libsdl-ttf2.0-dev_2.0.11-2_i386 +libsdl1.2-dev_1.2.15-5_i386 +libsdl2-dev_2.0.0+dfsg1-2~bpo70+1_i386 +libsdpa-dev_7.3.8+dfsg-1_i386 +libsearchclient-dev_0.7.7-3_i386 +libseaudit-dev_3.3.7-3_i386 +libseccomp-dev_2.1.1-1~bpo70+1_i386 +libseed-gtk3-dev_3.2.0-2_i386 +libsefs-dev_3.3.7-3_i386 +libselinux1-dev_2.1.9-5_i386 +libsemanage1-dev_2.1.6-6_i386 +libsensors-applet-plugin-dev_3.0.0-0.2_i386 +libsensors4-dev_1:3.3.2-2+deb7u1_i386 +libsepol1-dev_2.1.4-3_i386 +libserd-dev_0.14.0~dfsg0-2_i386 +libserf-dev_1.1.0-2_i386 +libsexplib-camlp4-dev_7.0.4-2_i386 +libsexy-dev_0.1.11-2+b1_i386 +libsfml-dev_1.6+dfsg2-2_i386 +libsfst1-1.2-0-dev_1.2.0-1.2_i386 +libsgutils2-dev_1.33-1_i386 +libsha-ocaml-dev_1.7-2+b2_i386 +libshairport-dev_1.2.1~git20120110.aeb4987-2_i386 +libshevek-dev_1.3-1_i386 +libshhmsg1-dev_1.4.1-4.1_i386 +libshhopt1-dev_1.1.7-2.1_i386 +libshiboken-dev_1.1.1-1_i386 +libshibsp-dev_2.4.3+dfsg-5+b1_i386 +libshibsp-dev_2.5.2+dfsg-2~bpo70+1_i386 +libshisa-dev_1.0.1-2_i386 +libshishi-dev_1.0.1-2_i386 +libshout-ocaml-dev_0.2.7-1+b3_i386 +libshout3-dev_2.2.2-8_i386 +libshp-dev_1.2.10-7_i386 +libshr-glib-dev_2011.03.08.2~git20110930-2_i386 +libsidl-dev_1.4.0.dfsg-8.1_i386 +libsidplay1-dev_1.36.59-5_i386 +libsidplay2-dev_2.1.1-14_i386 +libsidplayfp-dev_0.3.5-1_i386 +libsidutils-dev_2.1.1-14_i386 +libsieve2-dev_2.2.6-1.1_i386 +libsigc++-1.2-dev_1.2.7-2_i386 +libsigc++-2.0-dev_2.2.10-0.2_i386 +libsigc++-dev_1.0.4-9.4_i386 +libsigrok0-dev_0.1.0-2_i386 +libsigrokdecode0-dev_0.1.0-2_i386 +libsigsegv-dev_2.9-4_i386 +libsilly-dev_0.1.0-3_i386 +libsilo-dev_4.8-13_i386 +libsimage-dev_1.7.0-1.1+b1_i386 +libsimplelist0-dev_0.3.4-2_i386 +libsipwitch-dev_1.2.4-1_i386 +libsipxtapi-dev_3.3.0~test15-1~bpo70+1_i386 +libsiscone-dev_2.0.5-1_i386 +libsiscone-spherical-dev_2.0.5-1_i386 +libskk-dev_0.0.12-3_i386 +libskstream-0.3-dev_0.3.8-1_i386 +libslang2-dev_2.2.4-15_i386 +libslepc3.2-dev_3.2-p5-1_i386 +libslp-dev_1.2.1-9_i386 +libslurm-dev_2.3.4-2+b1_i386 +libslurmdb-dev_2.3.4-2+b1_i386 +libslv2-dev_0.6.6+dfsg1-2_i386 +libsm-dev_2:1.2.1-2_i386 +libsmbclient-dev_2:3.6.6-6+deb7u3_i386 +libsmbclient-dev_2:4.1.9+dfsg-1~bpo70+1_i386 +libsmbclient-raw-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libsmbios-dev_2.0.3.dfsg-1.1_i386 +libsmbsharemodes-dev_2:4.1.9+dfsg-1~bpo70+1_i386 +libsmf-dev_1.3-2_i386 +libsmi2-dev_0.4.8+dfsg2-7_i386 +libsmokekde-dev_4:4.8.4-1_i386 +libsmokeqt4-dev_4:4.8.4-1_i386 +libsmpeg-dev_0.4.5+cvs20030824-5_i386 +libsnacc-dev_1.3.1-1_i386 +libsnack2-dev_2.2.10-dfsg1-12.1_i386 +libsnappy-dev_1.0.5-2_i386 +libsndfile1-dev_1.0.25-5_i386 +libsndobj-dev_2.6.6.1-3_i386 +libsnmp-dev_5.4.3~dfsg-2.8_i386 +libsnmpkit-dev_0.9-16_i386 +libsocialweb-client-dev_0.25.20-2.1_i386 +libsocialweb-dev_0.25.20-2.1_i386 +libsocksd0-dev_1.1.19.dfsg-3+b3_i386 +libsofa-c-dev_2012.03.01-1_i386 +libsofa1-dev_1.0~beta4-7_i386 +libsofia-sip-ua-dev_1.12.11+20110422-1_i386 +libsofia-sip-ua-glib-dev_1.12.11+20110422-1_i386 +libsofthsm-dev_1.3.3-2_i386 +libsoil-dev_1.07~20080707.dfsg-2_i386 +libsombok-dev_2.2.1-1_i386 +libsonic-dev_0.1.17-1.1_i386 +libsope-dev_1.3.16-1_i386 +libsope-dev_2.0.5-1~bpo70+1_i386 +libsoprano-dev_2.7.6+dfsg.1-2wheezy1_i386 +libsoqt4-dev_1.5.0-2_i386 +libsoqt4-dev_1.6.0~e8310f-1~bpo70+1_i386 +libsord-dev_0.8.0~dfsg0-1_i386 +libsoundgen-dev_0.6-4_i386 +libsoundtouch-dev_1.6.0-3_i386 +libsoundtouch-ocaml-dev_0.1.7-1+b1_i386 +libsoup-gnome2.4-dev_2.38.1-3_i386 +libsoup2.4-dev_2.38.1-3_i386 +libsoupcutter-dev_1.1.7-1.2_i386 +libsource-highlight-dev_3.1.6-1.1_i386 +libsox-dev_14.4.0-3_i386 +libsp-gxmlcpp-dev_1.0.20040603-5_i386 +libsp1-dev_1.3.4-1.2.1-47.1+b1_i386 +libspandsp-dev_0.0.6~pre20-3.1_i386 +libsparsehash-dev_1.10-1_all +libsparskit-dev_2.0.0-2_i386 +libspatialindex-dev_1.7.0-1_i386 +libspatialite-dev_3.0.0~beta20110817-3+deb7u1_i386 +libspctag-dev_0.2-1_i386 +libspectre-dev_0.2.7-2_i386 +libspectrum-dev_1.0.0-3_i386 +libspeechd-dev_0.7.1-6.2_i386 +libspeex-dev_1.2~rc1-7_i386 +libspeex-ocaml-dev_0.2.0-1+b3_i386 +libspeexdsp-dev_1.2~rc1-7_i386 +libspf2-dev_1.2.9-7_i386 +libsphere-dev_3.2-4_i386 +libspice-client-glib-2.0-dev_0.12-5_i386 +libspice-client-gtk-2.0-dev_0.12-5_i386 +libspice-client-gtk-3.0-dev_0.12-5_i386 +libspice-protocol-dev_0.10.1-1_all +libspice-protocol-dev_0.12.6-1~bpo70+2_all +libspice-server-dev_0.11.0-1+deb7u1_i386 +libspice-server-dev_0.12.4-0nocelt2~bpo70+1_i386 +libspiro-dev_20071029-2_i386 +libspnav-dev_0.2.2-1_i386 +libspooles-dev_2.2-9_i386 +libsprng2-dev_2.0a-8_i386 +libsqlcipher-dev_2.2.1-2~bpo70+1_i386 +libsqlexpr-ocaml-dev_0.4.1-1+b5_i386 +libsqlheavy-dev_0.1.1-1_i386 +libsqlheavygtk-dev_0.1.1-1_i386 +libsqlite0-dev_2.8.17-7_i386 +libsqlite3-dev_3.7.13-1+deb7u1_i386 +libsqlite3-ocaml-dev_1.6.1-1+b1_i386 +libsquizz-dev_0.99a-2_i386 +libsratom-dev_0.2.0~dfsg0-1_i386 +libsrecord-dev_1.58-1+b1_i386 +libsrf-dev_0.1+dfsg-1_i386 +libsrtp0-dev_1.4.4+20100615~dfsg-2+deb7u1_i386 +libss7-dev_1.0.2-3_i386 +libsscm-dev_0.8.5-2.1_i386 +libssh-dev_0.5.4-1+deb7u1_i386 +libssh-dev_0.5.4-3~bpo70+1_i386 +libssh2-1-dev_1.4.2-1.1_i386 +libssl-dev_1.0.1e-2+deb7u7_i386 +libssl-ocaml-dev_0.4.6-1_i386 +libsslcommon2-dev_0.16-6+deb7u1_i386 +libssreflect-ocaml-dev_1.3pl4-1_i386 +libsss-sudo-dev_1.8.4-2_i386 +libst-dev_1.9-3_i386 +libstaden-read-dev_1.12.4-1_i386 +libstarlink-ast-dev_7.0.4+dfsg-1_i386 +libstarlink-pal-dev_0.1.0-1_i386 +libstarpu-contrib-dev_1.0.1+dfsg-1_i386 +libstarpu-dev_1.0.1+dfsg-1_i386 +libstartup-notification0-dev_0.12-1_i386 +libstatgrab-dev_0.17-1_i386 +libstdc++6-4.4-dev_4.4.7-2_i386 +libstdc++6-4.6-dev_4.6.3-14_i386 +libstdc++6-4.7-dev_4.7.2-5_i386 +libstemmer-dev_0+svn546-2_i386 +libsteptalk-dev_0.10.0-5+b1_i386 +libstfl-dev_0.22-1+b1_i386 +libstk0-dev_4.4.3-2_i386 +libstlport4.6-dev_4.6.2-7_i386 +libstonith1-dev_1.0.9+hg2665-1_i386 +libstonithd1-dev_1.1.7-1_i386 +libstreamanalyzer-dev_0.7.7-3_i386 +libstreams-dev_0.7.7-3_i386 +libstrigihtmlgui-dev_0.7.7-3_i386 +libstrigiqtdbusclient-dev_0.7.7-3_i386 +libstroke0-dev_0.5.1-6_i386 +libstxxl-dev_1.3.1-4_i386 +libsublime-dev_1.3.1-2_i386 +libsubtitleeditor-dev_0.33.0-1_i386 +libsubunit-dev_0.0.8+bzr176-1_i386 +libsugarext-dev_0.96.1-2_i386 +libsuil-dev_0.6.4~dfsg0-3_i386 +libsuitesparse-dev_1:3.4.0-3_i386 +libsuitesparse-metis-dev_3.1.0-2_i386 +libsundials-serial-dev_2.5.0-3_i386 +libsunpinyin-dev_2.0.3+git20120607-1_i386 +libsuperlu3-dev_3.0+20070106-3_i386 +libsvga1-dev_1:1.4.3-33_i386 +libsvm-dev_3.12-1_i386 +libsvn-dev_1.6.17dfsg-4+deb7u6_i386 +libsvncpp-dev_0.12.0dfsg-6_i386 +libsvnqt-dev_1.5.5-4.1_i386 +libsvrcore-dev_1:4.0.4-15_i386 +libswami-dev_2.0.0+svn389-2_i386 +libswe-dev_1.77.00.0005-2_i386 +libswiften-dev_2.0~beta1+dev47-1_i386 +libsword-dev_1.6.2+dfsg-5_i386 +libswscale-dev_6:0.8.10-1_i386 +libswscale-dev_6:10.1-1~bpo70+1_i386 +libsx-dev_2.05-3_i386 +libsyfi1.0-dev_1.0.0.dfsg-1_i386 +libsylph-dev_1.1.0-8_i386 +libsymmetrica-dev_2.0-1_i386 +libsynce0-dev_0.15-1.1_i386 +libsyncml-dev_0.5.4-2.1_i386 +libsynfig-dev_0.63.05-1_i386 +libsynopsis0.12-dev_0.12-8_i386 +libsynthesis-dev_3.4.0.16.7-1_i386 +libsynthesis-dev_3.4.0.47.1-1~bpo70+1_i386 +libsysactivity-dev_0.6.4-1_i386 +libsysfs-dev_2.1.0+repack-2_i386 +libsyslog-ng-dev_3.3.5-4_i386 +libsyslog-ocaml-dev_1.4-6+b2_i386 +libsystemd-daemon-dev_204-8~bpo70+1_i386 +libsystemd-daemon-dev_44-11+deb7u4_i386 +libsystemd-id128-dev_204-8~bpo70+1_i386 +libsystemd-id128-dev_44-11+deb7u4_i386 +libsystemd-journal-dev_204-8~bpo70+1_i386 +libsystemd-journal-dev_44-11+deb7u4_i386 +libsystemd-login-dev_204-8~bpo70+1_i386 +libsystemd-login-dev_44-11+deb7u4_i386 +libt1-dev_5.1.2-3.6_i386 +libtacacs+1-dev_4.0.4.19-11_all +libtachyon-dev_0.99~b2+dfsg-0.4_i386 +libtag-extras-dev_1.0.1-3_i386 +libtag1-dev_1.7.2-1_i386 +libtag1-dev_1.9.1-2~bpo70+1_i386 +libtagc0-dev_1.7.2-1_i386 +libtagc0-dev_1.9.1-2~bpo70+1_i386 +libtagcoll2-dev_2.0.13-1.1_i386 +libtaglib-cil-dev_2.0.4.0-1_all +libtaglib-ocaml-dev_0.2.0-1+b1_i386 +libtaktuk-1-dev_3.7.4-1_i386 +libtalloc-dev_2.0.7+git20120207-1_i386 +libtalloc-dev_2.1.1-1~bpo70+1_i386 +libtamuanova-dev_0.2-2_i386 +libtango7-dev_7.2.6+dfsg-14_i386 +libtango8-dev_8.1.2c+dfsg-4~bpo70+1_i386 +libtaningia-dev_0.2.2-1_i386 +libtaoframework-devil-cil-dev_2.1.svn20090801-9_all +libtaoframework-ffmpeg-cil-dev_2.1.svn20090801-9_all +libtaoframework-freeglut-cil-dev_2.1.svn20090801-9_all +libtaoframework-freetype-cil-dev_2.1.svn20090801-9_all +libtaoframework-ftgl-cil-dev_2.1.svn20090801-9_all +libtaoframework-lua-cil-dev_2.1.svn20090801-9_all +libtaoframework-ode-cil-dev_2.1.svn20090801-9_all +libtaoframework-openal-cil-dev_2.1.svn20090801-9_all +libtaoframework-opengl-cil-dev_2.1.svn20090801-9_all +libtaoframework-physfs-cil-dev_2.1.svn20090801-9_all +libtaoframework-sdl-cil-dev_2.1.svn20090801-9_all +libtar-dev_1.2.16-1+deb7u2_i386 +libtarantool-dev_1.4.6+20120629+2158-1_i386 +libtasn1-3-dev_2.13-2_i386 +libtasn1-6-dev_3.6-1~bpo70+1_i386 +libtbb-dev_4.0+r233-1_i386 +libtcc-dev_0.9.26~git20120612.ad5f375-6_i386 +libtcd-dev_2.2.2-1_i386 +libtclap-dev_1.2.1-1_i386 +libtclcl1-dev_1.20-6_i386 +libtcplay-dev_1.1-1~bpo70+1_i386 +libtdb-dev_1.2.10-2_i386 +libtdb-dev_1.2.12-1~bpo70+1_i386 +libtecla1-dev_1.6.1-5_i386 +libteem-dev_1.11.0~svn5226-1_i386 +libtelepathy-farstream-dev_0.4.0-3_i386 +libtelepathy-glib-dev_0.18.2-2_i386 +libtelepathy-logger-dev_0.4.0-1_i386 +libtelepathy-logger-qt4-dev_0.4.0-1_i386 +libtelepathy-qt4-dev_0.9.1-4_i386 +libtelnet-dev_0.21-1_i386 +libtemplates-parser11.6-dev_11.6-2_i386 +libterralib-dev_4.0.0-4_i386 +libtesseract-dev_3.02.01-6_i386 +libtet1.4-dev_1.4.3-1_i386 +libtevent-dev_0.9.16-1_i386 +libtevent-dev_0.9.21-1~bpo70+1_i386 +libtext-ocaml-dev_0.5-1+b2_i386 +libtextwrap-dev_0.1-13_i386 +libthai-dev_0.1.18-2_i386 +libtheora-dev_1.1.1+dfsg.1-3.1_i386 +libtheora-ocaml-dev_0.3.0-1+b3_i386 +libthepeg-dev_1.8.0-1_i386 +libthrust-dev_1.6.0-1_all +libthunar-vfs-1-dev_1.2.0-3+b1_i386 +libthunarx-2-dev_1.2.3-4+b1_i386 +libticables-dev_1.2.0-2_i386 +libticalcs-dev_1.1.3+dfsg1-1_i386 +libticonv-dev_1.1.0-1.1_i386 +libtidy-dev_20091223cvs-1.2_i386 +libtiff4-dev_3.9.6-11_i386 +libtiff5-alt-dev_4.0.2-6+deb7u2_i386 +libtiff5-dev_4.0.2-6+deb7u2_i386 +libtifiles-dev_1.1.1-1_i386 +libtimbl3-dev_6.4.2-1_i386 +libtimblserver2-dev_1.4-2_i386 +libtinfo-dev_5.9-10_i386 +libtinyxml-dev_2.6.2-1_i386 +libtinyxml2-dev_0~git20120518.1.a2ae54e-1_i386 +libtirpc-dev_0.2.2-5_i386 +libtk-img-dev_1:1.3-release-12_i386 +libtnt-dev_1.2.6-1_all +libtntdb-dev_1.2-2+b1_i386 +libtntnet-dev_2.1-2+deb7u1_i386 +libtododb-dev_0.11-3_i386 +libtogl-dev_1.7-12_all +libtokyocabinet-dev_1.4.47-2_i386 +libtokyotyrant-dev_1.1.40-4.1+b1_i386 +libtolua++5.1-dev_1.0.93-3_i386 +libtolua-dev_5.2.0-1_i386 +libtomcrypt-dev_1.17-3.2_i386 +libtommath-dev_0.42.0-1_i386 +libtomoe-dev_0.6.0-1.3_i386 +libtonezone-dev_1:2.5.0.1-2_i386 +libtophide-ocaml-dev_1.0.0-3_all +libtorch3-dev_3.1-2.1_i386 +libtorque2-dev_2.4.16+dfsg-1+deb7u2_i386 +libtorrent-dev_0.13.2-1_i386 +libtorrent-rasterbar-dev_0.15.10-1+b1_i386 +libtorture-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +libtotem-dev_3.0.1-8_i386 +libtotem-pg-dev_1.4.2-3_i386 +libtotem-plparser-dev_3.4.2-1_i386 +libtowitoko-dev_2.0.7-8.3_i386 +libtpl-dev_1.5-2_all +libtpm-unseal-dev_1.3.7-1_i386 +libtrace3-dev_3.0.14-1_i386 +libtracker-extract-0.14-dev_0.14.1-3_i386 +libtracker-miner-0.14-dev_0.14.1-3_i386 +libtracker-sparql-0.14-dev_0.14.1-3_i386 +libtrain-dev_0.9b-11_i386 +libtransitioner1-dev_1.1.7-1_i386 +libtre-dev_0.8.0-3_i386 +libtreil-dev_1.8-1.1_i386 +libtriangle-dev_1.6-2_i386 +libts-dev_1.0-11_i386 +libtse3-dev_0.3.1-4.3_i386 +libtsk-dev_3.2.3-2_i386 +libtsk-dev_4.1.3-3~bpo70+1_i386 +libtspi-dev_0.3.9-3+wheezy1_i386 +libttspico-dev_1.0+git20110131-2_i386 +libtulip-dev_3.7.0dfsg-4_i386 +libtumbler-1-dev_0.1.25-1+b1_i386 +libtut-dev_0.0.20070706-1_all +libtuxcap-dev_1.4.0.dfsg2-2.1_i386 +libtwin-dev_12.04.13.17.57-g130ee5f-2_i386 +libtwofish-dev_0.3-3_i386 +libtwolame-dev_0.3.13-1_i386 +libtxc-dxtn-s2tc-dev_0~git20110809-3_i386 +libtype-conv-camlp4-dev_3.0.4-1_i386 +libtyxml-ocaml-dev_2.1-1_i386 +libu1db-dev_0.1.4-2~bpo70+1_i386 +libuchardet-dev_0.0.1-1_i386 +libucimf-dev_2.3.8-4_i386 +libucl-dev_1.03-5_i386 +libuclmmbase1-dev_1.2.16.0-1_i386 +libucommon-dev_5.2.2-4_i386 +libucto1-dev_0.5.2-2_i386 +libudev-dev_175-7.2_i386 +libudev-dev_204-8~bpo70+1_i386 +libudf-dev_0.83-4_i386 +libudt-dev_4.10+dfsg-1_i386 +libudunits2-dev_2.1.23-3_i386 +libuhd-dev_3.4.2-1_i386 +libuhd-dev_3.5.5-1~bpo70+1_i386 +libuim-dev_1:1.8.1-4_i386 +libumlib-dev_0.8.2-1_i386 +libunac1-dev_1.8.0-6_i386 +libunbound-dev_1.4.17-3_i386 +libunbound-dev_1.4.22-1~bpo70+1_i386 +libunicap2-dev_0.9.12-2_i386 +libuninameslist-dev_0.0.20091231-1.1_i386 +libuninum-dev_2.7-1.1_i386 +libunique-3.0-dev_3.0.2-1_i386 +libunique-dev_1.1.6-4_i386 +libunistring-dev_0.9.3-5_i386 +libunittest++-dev_1.4.0-3_i386 +libunshield-dev_0.6-3_i386 +libunwind-setjmp0-dev_0.99-0.3_i386 +libunwind7-dev_0.99-0.3_i386 +libupnp-dev_1:1.6.17-1.2_all +libupnp4-dev_1.8.0~svn20100507-1.2_i386 +libupnp6-dev_1:1.6.17-1.2_i386 +libupower-glib-dev_0.9.17-1_i386 +libupsclient1-dev_2.6.4-2.3+deb7u1_i386 +libupse-dev_1.0.0-1_i386 +libuptimed-dev_1:0.3.17-3.1_i386 +liburcu-dev_0.6.7-2_i386 +liburfkill-glib-dev_0.3.0-1_i386 +liburg0-dev_0.8.12-4_i386 +liburiparser-dev_0.7.5-1_i386 +libusb++-dev_2:0.1.12-20+nmu1_i386 +libusb-1.0-0-dev_2:1.0.11-1_i386 +libusb-1.0-0-dev_2:1.0.18-2~bpo70+1_i386 +libusb-dev_2:0.1.12-20+nmu1_i386 +libusb-ocaml-dev_1.2.0-2+b7_i386 +libusbip-dev_1.1.1+3.14-1~bpo70+1_i386 +libusbip-dev_1.1.1+3.2.17-1_i386 +libusbmuxd-dev_1.0.7-2_i386 +libusbprog-dev_0.2.0-2_i386 +libusbredirhost-dev_0.4.3-2_i386 +libusbredirhost-dev_0.6-2~bpo70+1_i386 +libusbredirparser-dev_0.4.3-2_i386 +libusbredirparser-dev_0.6-2~bpo70+1_i386 +libusbtc08-dev_1.7.2-1_i386 +libuser1-dev_1:0.56.9.dfsg.1-1.2_i386 +libust-dev_2.0.4-1_i386 +libustr-dev_1.0.4-3_i386 +libutempter-dev_1.1.5-4_i386 +libuu-dev_0.5.20-3.3_i386 +libuuidm-ocaml-dev_0.9.4-1_i386 +libv4l-dev_0.8.8-3_i386 +libv8-3.14-dev_3.14.5.8-8~bpo70+1_i386 +libv8-dev_3.14.5.8-8~bpo70+1_i386 +libv8-dev_3.8.9.20-2_i386 +libv8-i18n-dev_0~0.svn7-3_i386 +libva-dev_1.0.15-4_i386 +libvala-0.14-dev_0.14.2-2_i386 +libvala-0.16-dev_0.16.1-2_i386 +libvaladoc-dev_0.3.2~git20120227-1_i386 +libvalhalla-dev_2.0.0-4+b1_i386 +libvanessa-adt-dev_0.0.9-1_i386 +libvanessa-logger-dev_0.0.10-1.1_i386 +libvanessa-socket-dev_0.0.12-1_i386 +libvarconf-dev_0.6.7-2_i386 +libvarnishapi-dev_3.0.2-2+deb7u1_i386 +libvbr-dev_2.6.8-4_i386 +libvc-dev_003.dfsg.1-12_i386 +libvcdinfo-dev_0.7.24+dfsg-0.1_i386 +libvde-dev_2.3.2-4_i386 +libvdeplug-dev_2.3.2-4_i386 +libvdk2-dev_2.4.0-5.3_i386 +libvdkbuilder2-dev_2.4.0-4.3_i386 +libvdkxdb2-dev_2.4.0-3.4_i386 +libvdpau-dev_0.4.1-7_i386 +libventrilo-dev_1.2.4-1_i386 +libverbiste-dev_0.1.34-1_i386 +libverto-dev_0.2.2-1_i386 +libvformat-dev_1.13-10_i386 +libvia-dev_2.0.4-2_i386 +libvibrant6-dev_6.1.20120620-2_i386 +libviennacl-dev_1.2.0-2_all +libview-dev_0.6.6-2.1_i386 +libvigraimpex-dev_1.7.1+dfsg1-3_i386 +libvips-dev_7.28.5-1+deb7u1_i386 +libvirt-dev_0.9.12.3-1_i386 +libvirt-dev_1.2.4-1~bpo70+1_i386 +libvirt-glib-1.0-dev_0.0.8-1_i386 +libvirt-ocaml-dev_0.6.1.2-1_i386 +libvisca-dev_1.0.1-1_i386 +libvisio-dev_0.0.17-1_i386 +libvisual-0.4-dev_0.4.0-5_i386 +libvlc-dev_2.0.3-5_i386 +libvlc-dev_2.1.2-2~bpo70+3_i386 +libvlccore-dev_2.0.3-5_i386 +libvlccore-dev_2.1.2-2~bpo70+3_i386 +libvmmlib-dev_1.0-2_all +libvmtk-dev_1.0.1-1_i386 +libvncserver-dev_0.9.9+dfsg-1_i386 +libvo-aacenc-dev_0.1.2-1_i386 +libvo-amrwbenc-dev_0.1.2-1_i386 +libvoaacenc-ocaml-dev_0.1.0-1+b1_i386 +libvoikko-dev_3.5-1.1_i386 +libvolpack1-dev_1.0b3-3_i386 +libvorbis-dev_1.3.2-1.3_i386 +libvorbis-ocaml-dev_0.6.1-1+b1_i386 +libvorbisidec-dev_1.0.2+svn18153-0.2_i386 +libvotequorum-dev_1.4.2-3_i386 +libvpb-dev_4.2.55-1_i386 +libvpx-dev_1.1.0-1_i386 +libvrb0-dev_0.5.1-5.1_i386 +libvte-2.90-dev_1:0.32.2-1_i386 +libvte-dev_1:0.28.2-5_i386 +libvte0.16-cil-dev_2.26.0-8_i386 +libvtk5-dev_5.8.0-13+b1_i386 +libvtk5-qt4-dev_5.8.0-13+b1_i386 +libvtkedge-dev_0.2.0~20110819-2_i386 +libvtkgdcm2-dev_2.2.0-14.1_i386 +libvxl1-dev_1.14.0-18_i386 +libwacom-dev_0.6-1_i386 +libwaei-dev_3.4.3-1_i386 +libwaili-dev_19990723-20_i386 +libwavefront-standalone3.0-dev_3.0.2+dfsg-4+b1_i386 +libwavpack-dev_4.60.1-3_i386 +libwayland-dev_0.85.0-2_i386 +libwbclient-dev_2:3.6.6-6+deb7u3_i386 +libwbclient-dev_2:4.1.9+dfsg-1~bpo70+1_i386 +libwbxml2-dev_0.10.7-1_i386 +libwcstools-dev_3.8.5-1_i386 +libwebauth-dev_4.1.1-2_i386 +libwebauth-dev_4.6.0-2~bpo70+1_i386 +libwebcam0-dev_0.2.2-1_i386 +libwebkit-cil-dev_0.3-6_all +libwebkit-dev_1.8.1-3.4_i386 +libwebkitgtk-3.0-dev_1.8.1-3.4_i386 +libwebkitgtk-dev_1.8.1-3.4_i386 +libwebp-dev_0.1.3-3+nmu1_i386 +libwebrtc-audio-processing-dev_0.1-2_i386 +libweed-dev_1.6.2~ds1-2_i386 +libwfmath-0.3-dev_0.3.12-3_i386 +libwfut-0.2-dev_0.2.1-2_i386 +libwibble-dev_0.1.28-1.1_i386 +libwildmidi-dev_0.2.3.4-2.1_i386 +libwine-dev_1.4.1-4_i386 +libwings-dev_0.95.3-2_i386 +libwireshark-dev_1.10.7-1~bpo70+1_i386 +libwireshark-dev_1.8.2-5wheezy10_i386 +libwiretap-dev_1.10.7-1~bpo70+1_i386 +libwiretap-dev_1.8.2-5wheezy10_i386 +libwmf-dev_0.2.8.4-10.3_i386 +libwnck-3-dev_3.4.2-1_i386 +libwnck-dev_2.30.7-1_i386 +libwnck1.0-cil-dev_2.26.0-8_i386 +libwnn-dev_1.1.1~a021+cvs20100325-6_i386 +libwnn6-dev_1.0.0-14.2+b1_i386 +libwpd-dev_0.9.4-3_i386 +libwpg-dev_0.2.1-1_i386 +libwps-dev_0.2.7-1_i386 +libwrap0-dev_7.6.q-24_i386 +libwraster3-dev_0.95.3-2_i386 +libwreport-dev_2.4-1_i386 +libwsutil-dev_1.10.7-1~bpo70+1_i386 +libwsutil-dev_1.8.2-5wheezy10_i386 +libwt-dev_3.2.1-2_i386 +libwtdbo-dev_3.2.1-2_i386 +libwtdbofirebird-dev_3.2.1-2_i386 +libwtdbopostgres-dev_3.2.1-2_i386 +libwtdbosqlite-dev_3.2.1-2_i386 +libwtext-dev_3.2.1-2_i386 +libwtfcgi-dev_3.2.1-2_i386 +libwthttp-dev_3.2.1-2_i386 +libwttest-dev_3.2.1-2_i386 +libwv-dev_1.2.9-3_i386 +libwv2-dev_0.4.2.dfsg.2-1~deb7u1_i386 +libwvstreams-dev_4.6.1-5_i386 +libwxbase2.8-dev_2.8.12.1-12_i386 +libwxgtk2.8-dev_2.8.12.1-12_i386 +libwxsmithlib-dev_10.05-2.1_i386 +libwxsmithlib-dev_13.12-1~bpo70+1_i386 +libwxsmithlib0-dev_10.05-2.1_i386 +libwxsmithlib0-dev_13.12-1~bpo70+1_i386 +libwxsqlite3-2.8-dev_3.0.0.1~dfsg0-2_i386 +libwxsvg-dev_2:1.1.8~dfsg0-2_i386 +libx11-dev_2:1.5.0-1+deb7u1_i386 +libx11-xcb-dev_2:1.5.0-1+deb7u1_i386 +libx264-dev_2:0.123.2189+git35cf912-1_i386 +libx52pro-dev_0.1.1-2.1_i386 +libx86-dev_1.1+ds1-10_i386 +libxalan110-dev_1.10-6_i386 +libxapian-dev_1.2.12-2_i386 +libxapian-dev_1.2.16-2~bpo70+1_i386 +libxatracker-dev_8.0.5-4+deb7u2_i386 +libxau-dev_1:1.0.7-1_i386 +libxaw7-dev_2:1.0.10-2_i386 +libxbae-dev_4.60.4-3_i386 +libxbase2.0-dev_2.0.0-8.5_i386 +libxcb-composite0-dev_1.8.1-2+deb7u1_i386 +libxcb-cursor-dev_0.1.1-2~bpo70+1_i386 +libxcb-damage0-dev_1.8.1-2+deb7u1_i386 +libxcb-dpms0-dev_1.8.1-2+deb7u1_i386 +libxcb-dri2-0-dev_1.8.1-2+deb7u1_i386 +libxcb-ewmh-dev_0.3.9-2_i386 +libxcb-glx0-dev_1.8.1-2+deb7u1_i386 +libxcb-icccm4-dev_0.3.9-2_i386 +libxcb-image0-dev_0.3.9-1_i386 +libxcb-keysyms1-dev_0.3.9-1_i386 +libxcb-randr0-dev_1.8.1-2+deb7u1_i386 +libxcb-record0-dev_1.8.1-2+deb7u1_i386 +libxcb-render-util0-dev_0.3.8-1.1_i386 +libxcb-render0-dev_1.8.1-2+deb7u1_i386 +libxcb-res0-dev_1.8.1-2+deb7u1_i386 +libxcb-screensaver0-dev_1.8.1-2+deb7u1_i386 +libxcb-shape0-dev_1.8.1-2+deb7u1_i386 +libxcb-shm0-dev_1.8.1-2+deb7u1_i386 +libxcb-sync0-dev_1.8.1-2+deb7u1_i386 +libxcb-util0-dev_0.3.8-2_i386 +libxcb-xevie0-dev_1.8.1-2+deb7u1_i386 +libxcb-xf86dri0-dev_1.8.1-2+deb7u1_i386 +libxcb-xfixes0-dev_1.8.1-2+deb7u1_i386 +libxcb-xinerama0-dev_1.8.1-2+deb7u1_i386 +libxcb-xprint0-dev_1.8.1-2+deb7u1_i386 +libxcb-xtest0-dev_1.8.1-2+deb7u1_i386 +libxcb-xv0-dev_1.8.1-2+deb7u1_i386 +libxcb-xvmc0-dev_1.8.1-2+deb7u1_i386 +libxcb1-dev_1.8.1-2+deb7u1_i386 +libxcomp-dev_3.5.0.12-1+b1_i386 +libxcomposite-dev_1:0.4.3-2_i386 +libxcp-ocaml-dev_0.5.2-3+b1_i386 +libxcrypt-dev_1:2.4-3_i386 +libxcursor-dev_1:1.1.13-1+deb7u1_i386 +libxdamage-dev_1:1.1.3-2_i386 +libxdb-dev_1.2.0-7.2_i386 +libxdelta2-dev_1.1.3-9_i386 +libxdffileio-dev_0.3-1_i386 +libxdg-basedir-dev_1.1.1-2_i386 +libxdmcp-dev_1:1.1.1-1_i386 +libxdmf-dev_2.1.dfsg.1-5_i386 +libxdo-dev_1:2.20100701.2961-3+deb7u3_i386 +libxen-dev_4.1.4-3+deb7u1_i386 +libxen-ocaml-dev_4.1.4-3+deb7u1_i386 +libxenapi-ocaml-dev_1.3.2-15_i386 +libxenomai-dev_2.6.0-2_i386 +libxerces-c-dev_3.1.1-3_i386 +libxerces-c2-dev_2.8.0+deb1-3_i386 +libxext-dev_2:1.3.1-2+deb7u1_i386 +libxfce4menu-0.1-dev_4.6.2-1_i386 +libxfce4ui-1-dev_4.8.1-1_i386 +libxfce4util-dev_4.8.2-1_i386 +libxfcegui4-dev_4.8.1-5_i386 +libxfconf-0-dev_4.8.1-1_i386 +libxfixes-dev_1:5.0-4+deb7u1_i386 +libxfont-dev_1:1.4.5-3_i386 +libxft-dev_2.3.1-1_i386 +libxi-dev_2:1.6.1-1+deb7u1_i386 +libxine-dev_1.1.21-1+deb7u1_i386 +libxine2-dev_1.2.2-5_i386 +libxinerama-dev_2:1.1.2-1+deb7u1_i386 +libxkbfile-dev_1:1.0.8-1_i386 +libxklavier-dev_5.2.1-1_i386 +libxml++2.6-dev_2.34.2-1_i386 +libxml-light-ocaml-dev_2.2-15_i386 +libxml-security-c-dev_1.6.1-5+deb7u2_i386 +libxml-security-c-dev_1.7.2-2~bpo70+1_i386 +libxml2-dev_2.8.0+dfsg1-7+nmu3_i386 +libxmlada4.1-dev_4.1-2_i386 +libxmlezout2-dev_1.06.1-5_i386 +libxmlm-ocaml-dev_1.1.0-1_i386 +libxmlplaylist-ocaml-dev_0.1.3-1+b2_i386 +libxmlrpc-c++4-dev_1.16.33-3.2_i386 +libxmlrpc-c3-dev_1.16.33-3.2_all +libxmlrpc-core-c3-dev_1.16.33-3.2_i386 +libxmlrpc-epi-dev_0.54.2-1_i386 +libxmlrpc-light-ocaml-dev_0.6.1-3+b5_i386 +libxmlsec1-dev_1.2.18-2_i386 +libxmltok1-dev_1.2-3_i386 +libxmltooling-dev_1.4.2-5_i386 +libxmltooling-dev_1.5.3-2~bpo70+1_i386 +libxmmsclient++-dev_0.8+dfsg-4_i386 +libxmmsclient++-glib-dev_0.8+dfsg-4_i386 +libxmmsclient-dev_0.8+dfsg-4_i386 +libxmmsclient-glib-dev_0.8+dfsg-4_i386 +libxmpi4-dev_2.2.3b8-13_i386 +libxmu-dev_2:1.1.1-1_i386 +libxmuu-dev_2:1.1.1-1_i386 +libxnee-dev_3.13-1_i386 +libxneur-dev_0.15.0-1.1_i386 +libxnvctrl-dev_304.88-1_i386 +libxnvctrl-dev_319.72-1~bpo70+1_i386 +libxosd-dev_2.2.14-2_i386 +libxp-dev_1:1.0.1-2+deb7u1_i386 +libxpa-dev_2.1.14-2_i386 +libxplc0.3.13-dev_0.3.13-3_i386 +libxpm-dev_1:3.5.10-1_i386 +libxqdbm-dev_1.8.78-2_i386 +libxqilla-dev_2.3.0-1_i386 +libxr1-dev_1.0-2.1_i386 +libxrandr-dev_2:1.3.2-2+deb7u1_i386 +libxrender-dev_1:0.9.7-1+deb7u1_i386 +libxres-dev_2:1.0.6-1+deb7u1_i386 +libxsettings-client-dev_0.17-6_i386 +libxsettings-dev_0.11-3_i386 +libxslt1-dev_1.1.26-14.1_i386 +libxss-dev_1:1.2.2-1_i386 +libxstr-ocaml-dev_0.2.1-21+b3_i386 +libxstrp4-camlp4-dev_1.8-3_all +libxt-dev_1:1.1.3-1+deb7u1_i386 +libxtst-dev_2:1.2.1-1+deb7u1_i386 +libxtuplecommon-dev_4.1.0-3~bpo70+1_i386 +libxv-dev_2:1.0.7-1+deb7u1_i386 +libxvbaw-dev_1:12-6+point-3_i386 +libxvbaw-dev_1:13.12-4~bpo70+1_i386 +libxvidcore-dev_2:1.3.2-9_i386 +libxvmc-dev_2:1.0.7-1+deb7u2_i386 +libxxf86dga-dev_2:1.1.3-2+deb7u1_i386 +libxxf86vm-dev_1:1.1.2-1+deb7u1_i386 +libxy-dev_0.8-1+b1_i386 +libyahoo2-dev_1.0.1-1_i386 +libyajl-dev_2.0.4-2_i386 +libyaml-cpp-dev_0.3.0-1_i386 +libyaml-dev_0.1.4-2+deb7u4_i386 +libyara-dev_2.0.0-2~bpo70+1_i386 +libyaz4-dev_4.2.30-2_i386 +libydpdict2-dev_1.0.2-1_i386 +libyelp-dev_3.4.2-1+b1_i386 +libygl4-dev_4.2e-4_i386 +libykclient-dev_2.6-1_i386 +libykpers-1-dev_1.7.0-1_i386 +libyojson-ocaml-dev_1.0.3-1_i386 +libytnef0-dev_1.5-4_i386 +libyubikey-dev_1.8-1_i386 +libz80ex-dev_1.1.19-3_i386 +libzarith-ocaml-dev_1.1-2_i386 +libzbar-dev_0.10+doc-8_i386 +libzbargtk-dev_0.10+doc-8_i386 +libzbarqt-dev_0.10+doc-8_i386 +libzeep-dev_2.9.0-2_i386 +libzeitgeist-cil-dev_0.8.0.0-4_all +libzeitgeist-dev_0.3.18-1_i386 +libzen-dev_0.4.27-2_i386 +libzephyr-dev_3.0.2-2_i386 +libzerg0-dev_1.0.7-3_i386 +libzeroc-ice34-dev_3.4.2-8.2_i386 +libzinnia-dev_0.06-1+b1_i386 +libzip-dev_0.10.1-1.1_i386 +libzip-ocaml-dev_1.04-6+b3_i386 +libzipios++-dev_0.1.5.9+cvs.2007.04.28-5.1_i386 +libzita-alsa-pcmi-dev_0.2.0-1_i386 +libzita-convolver-dev_3.1.0-2_i386 +libzita-resampler-dev_1.1.0-3_i386 +libzlcore-dev_0.12.10dfsg-8_i386 +libzltext-dev_0.12.10dfsg-8_i386 +libzmq-dev_2.2.0+dfsg-2_i386 +libzmq3-dev_3.2.3+dfsg-2~bpo70+1_i386 +libzn-poly-dev_0.8-1.1_i386 +libzookeeper-mt-dev_3.3.5+dfsg1-2_i386 +libzookeeper-st-dev_3.3.5+dfsg1-2_i386 +libzorp-dev_3.9.5-4_i386 +libzorpll-dev_3.9.1.3-1_i386 +libzrtpcpp-dev_2.0.0-3_i386 +libzthread-dev_2.3.2-7_i386 +libzvbi-dev_0.2.33-6_i386 +libzzip-dev_0.13.56-1.1_i386 +licq-dev_1.6.1-3_all +linux-libc-dev_3.14.7-1~bpo70+1_i386 +linux-libc-dev_3.2.57-3_i386 +lldpad-dev_0.9.44-1_i386 +llvm-2.9-dev_2.9+dfsg-7_i386 +llvm-3.0-dev_3.0-10_i386 +llvm-3.1-dev_3.1-1_i386 +llvm-dev_1:3.0-14+nmu2_i386 +lttv-dev_0.12.38-21032011-1+b1_i386 +lua-apr-dev_0.23.2-1_i386 +lua-bitop-dev_1.0.2-1_i386 +lua-curl-dev_0.3.0-7_i386 +lua-curses-dev_5.1.19-2_i386 +lua-cyrussasl-dev_1.0.0-4_i386 +lua-dbi-mysql-dev_0.5+svn78-4_i386 +lua-dbi-postgresql-dev_0.5+svn78-4_i386 +lua-dbi-sqlite3-dev_0.5+svn78-4_i386 +lua-event-dev_0.4.1-2_i386 +lua-expat-dev_1.2.0-5+deb7u1_i386 +lua-filesystem-dev_1.5.0+16+g84f1af5-1_i386 +lua-iconv-dev_7-1_i386 +lua-ldap-dev_1.1.0-1-geeac494-3_i386 +lua-leg-dev_0.1.2-8_all +lua-lgi-dev_0.6.2-1_i386 +lua-lpeg-dev_0.10.2-5_i386 +lua-md5-dev_1.1.2-6_i386 +lua-penlight-dev_1.0.2+htmldoc-2_all +lua-posix-dev_5.1.19-2_i386 +lua-rex-onig-dev_2.6.0-2_i386 +lua-rex-pcre-dev_2.6.0-2_i386 +lua-rex-posix-dev_2.6.0-2_i386 +lua-rex-tre-dev_2.6.0-2_i386 +lua-rings-dev_1.2.3-1_i386 +lua-sec-dev_0.4.1+git063e8a8-2~bpo70+1_i386 +lua-sec-dev_0.4.1-1_i386 +lua-socket-dev_2.0.2-8_i386 +lua-socket-dev_3.0~rc1-3~bpo70+1_i386 +lua-sql-mysql-dev_2.3.0-1+build0_i386 +lua-sql-postgres-dev_2.3.0-1+build0_i386 +lua-sql-sqlite3-dev_2.3.0-1+build0_i386 +lua-svn-dev_0.4.0-7_i386 +lua-wsapi-fcgi-dev_1.5-3_i386 +lua-zip-dev_1.2.3-11_i386 +lua-zlib-dev_0.2-1_i386 +lua5.1-policy-dev_33_all +lv2-dev_1.0.0~dfsg2-2_i386 +lxc-dev_0.8.0~rc1-8+deb7u2_i386 +lzma-dev_9.22-2_all +manpages-de-dev_1.2-1_all +manpages-dev_3.44-1_all +manpages-fr-dev_3.44d1p1-1_all +manpages-ja-dev_0.5.0.0.20120606-1_all +manpages-pl-dev_1:0.3-1_all +manpages-posix-dev_2.16-1_all +manpages-pt-dev_20040726-4_all +matlab-support-dev_0.0.18_all +mdbtools-dev_0.7-1+deb7u1_i386 +med-bio-dev_1.13.2_all +med-imaging-dev_1.13.2_all +mesa-common-dev_8.0.5-4+deb7u2_i386 +mffm-fftw-dev_1.7-3_i386 +mffm-timecode-dev_1.6-2_all +mingw-w64-dev_2.0.3-1_all +mingw-w64-i686-dev_2.0.3-1_all +mingw-w64-x86-64-dev_2.0.3-1_all +minpack-dev_19961126+dfsg1-1_i386 +mongodb-dev_1:2.0.6-1.1_i386 +mongodb-dev_1:2.4.8-2~bpo70+1_i386 +mpi-default-dev_1.0.1_i386 +muroard-dev_0.1.10-2_i386 +neko-dev_1.8.1-6_all +nettle-dev_2.4-3_i386 +nettle-dev_2.7.1-1~bpo70+1_i386 +network-manager-dev_0.9.4.0-10_i386 +nodejs-dev_0.10.26~dfsg1-1~bpo70+1_i386 +notion-dev_3+2012042300-1_all +ntfs-3g-dev_1:2012.1.15AR.5-2.1_i386 +nvidia-cg-dev_3.1.0013-1_i386 +nvidia-cuda-dev_4.2.9-2_i386 +nvidia-cuda-dev_5.0.35-8~bpo70+1_i386 +nvidia-glx-legacy-71xx-dev_71.86.15-3_i386 +nvidia-glx-legacy-dev_71.86.15-3_i386 +nvidia-opencl-dev_4.2.9-2_i386 +nvidia-opencl-dev_5.0.35-8~bpo70+1_i386 +ocfs2-tools-dev_1.6.4-1+deb7u1_i386 +ocl-icd-dev_1.3-3_i386 +ocl-icd-opencl-dev_1.3-3_i386 +ocsigen-dev_1.3.4-2_all +octave-pkg-dev_1.0.2_all +ofono-dev_1.6-2_all +okteta-dev_4:4.8.4+dfsg-1_i386 +okular-dev_4:4.8.4-3_i386 +open-vm-tools-dev_2:8.8.0+2012.05.21-724730-1+nmu2_all +open-vm-tools-dev_2:9.4.0-1280544-8~bpo70+1_all +openais-dev_1.1.4-4.1_i386 +openbox-dev_3.5.0-7_i386 +openbox-dev_3.5.2-6~bpo70+1_i386 +openchangeserver-dev_1:1.0-3_i386 +openchangeserver-dev_1:2.1-1~bpo70+1_i386 +oss4-dev_4.2-build2006-2+deb7u1_all +pacemaker-dev_1.1.7-1_i386 +packaging-dev_0.4_all +paraview-dev_3.14.1-6_i386 +paraview-dev_4.0.1-1~bpo70+1_i386 +parole-dev_0.2.0.6-1+b1_i386 +parser3-dev_3.4.2-2_i386 +pbs-drmaa-dev_1.0.10-3_i386 +petsc-dev_3.2.dfsg-6_all +php5-dev_5.4.4-14+deb7u9_i386 +pidgin-dev_2.10.7-2~bpo70+1_all +pidgin-dev_2.10.9-1~deb7u1_all +pike7.8-dev_7.8.700-7~bpo70+1_i386 +pinball-dev_0.3.1-13.1_i386 +planetpenguin-racer-gimp-dev_0.4-5_all +planner-dev_0.14.6-1_i386 +plplot-tcl-dev_5.9.9-5_i386 +plptools-dev_1.0.9-2.4_i386 +pluma-dev_1.8.1+dfsg1-2~bpo70+1_i386 +plymouth-dev_0.8.5.1-5_i386 +portaudio19-dev_19+svn20111121-1_i386 +postfix-dev_2.11.1-1~bpo70+1_all +postfix-dev_2.9.6-2_all +ppl-dev_0.11.2-8_i386 +ppp-dev_2.4.5-5.1_all +prayer-templates-dev_1.3.4-dfsg1-1_i386 +proftpd-dev_1.3.4a-5+deb7u1_i386 +pslib-dev_0.4.5-3_i386 +publib-dev_0.40-1_i386 +puredata-dev_0.43.2-5_all +pvm-dev_3.4.5-12.5_i386 +pxlib-dev_0.6.5-1_i386 +python-all-dev_2.7.3-4+deb7u1_all +python-apt-dev_0.8.8.2_all +python-cairo-dev_1.8.8-1_all +python-cxx-dev_6.2.4-3_all +python-dbus-dev_1.1.1-1_all +python-dev_2.7.3-4+deb7u1_all +python-egenix-mx-base-dev_3.2.1-1.1_all +python-gamera-dev_3.3.3-2_all +python-gi-dev_3.2.2-2_all +python-gnome2-desktop-dev_2.32.0+dfsg-2_all +python-gnome2-dev_2.28.1+dfsg-1_all +python-gnome2-extras-dev_2.25.3-12_all +python-gobject-2-dev_2.28.6-10_all +python-gobject-dev_3.2.2-2_all +python-greenlet-dev_0.3.1-2.5_i386 +python-gst0.10-dev_0.10.22-3_i386 +python-gtk2-dev_2.24.0-3_all +python-kde4-dev_4:4.8.4-1_all +python-ldb-dev_1:1.1.16-1~bpo70+1_i386 +python-ldb-dev_1:1.1.6-1_i386 +python-openturns-dev_1.0-4_i386 +python-pyorbit-dev_2.24.0-6_all +python-qt4-dev_4.9.3-4_all +python-sip-dev_4.13.3-2_i386 +python-talloc-dev_2.0.7+git20120207-1_i386 +python-talloc-dev_2.1.1-1~bpo70+1_i386 +python-webkit-dev_1.1.8-2_all +python2.6-dev_2.6.8-1.1_i386 +python2.7-dev_2.7.3-6+deb7u2_i386 +python3-all-dev_3.2.3-6_all +python3-cairo-dev_1.10.0+dfsg-2_all +python3-cxx-dev_6.2.4-3_all +python3-dev_3.2.3-6_all +python3-sip-dev_4.13.3-2_i386 +python3.2-dev_3.2.3-7_i386 +qmf-dev_1.0.7~2011w23.2-2.1_i386 +qtkeychain-dev_0.1.0-2~bpo70+1_i386 +qtmobility-dev_1.2.0-3_i386 +r-base-dev_2.15.1-4_all +regina-normal-dev_4.93-1_i386 +resource-agents-dev_1:3.9.2-5+deb7u2_i386 +rhythmbox-dev_2.97-2.1_i386 +rivet-plugins-dev_1.8.0-1_all +roarplaylistd-dev_0.1.1-2_all +robot-player-dev_3.0.2+dfsg-4_all +ruby-dev_1:1.9.3_all +ruby-gnome2-dev_1.1.3-2+b1_i386 +ruby1.8-dev_1.8.7.358-7.1+deb7u1_i386 +ruby1.9.1-dev_1.9.3.194-8.1+deb7u2_i386 +rygel-1.0-dev_0.14.3-2+deb7u1_i386 +samba-dev_2:4.1.9+dfsg-1~bpo70+1_i386 +samba4-dev_4.0.0~beta2+dfsg1-3.2+deb7u2_i386 +sbnc-php-dev_1.2-26_all +science-astronomy-dev_1.0_all +science-dataacquisition-dev_1.0_all +science-engineering-dev_1.0_all +science-highenergy-physics-dev_1.0_all +science-mathematics-dev_1.0_all +science-meteorology-dev_1.0_all +science-nanoscale-physics-dev_1.0_all +science-physics-dev_1.0_all +scim-dev_1.4.13-5_all +sciplot-dev_1.36-15_i386 +selinux-policy-dev_2:2.20110726-12_all +seqan-dev_1.3.1-1_all +sfftw-dev_2.1.5-1_i386 +skalibs-dev_0.47-1_i386 +slurm-drmaa-dev_1.0.4-3_i386 +slurm-llnl-basic-plugins-dev_2.3.4-2+b1_i386 +snappea-dev_3.0d3-22_i386 +spl-dev_1.0~pre6-3.1+b1_i386 +sra-toolkit-libs-dev_2.1.7a-1_i386 +ss-dev_2.0-1.42.5-1.1_i386 +stx-btree-dev_0.8.6-1_all +styx-dev_1.8.0-1.1_i386 +supercollider-dev_1:3.4.5-1wheezy1_i386 +svdrpservice-dev_0.0.4-14_all +sweep-dev_0.9.3-6_all +swish-e-dev_2.4.7-3_i386 +syfi-dev_1.0.0.dfsg-1_all +system-tools-backends-dev_2.10.2-1_all +systemtap-sdt-dev_1.7-1+deb7u1_i386 +tcl-dev_8.5.0-2.1_all +tcl-memchan-dev_2.3-2_i386 +tcl-trf-dev_2.1.4-dfsg1-1_i386 +tcl8.4-dev_8.4.19-5_i386 +tcl8.5-dev_8.5.11-2_i386 +tclcl-dev_1.20-6_all +tclx8.4-dev_8.4.0-3_i386 +tclxml-dev_3.3~svn11-2_i386 +tdom-dev_0.8.3~20080525-3+nmu2_i386 +tesseract-ocr-dev_3.02.01-6_all +tix-dev_8.4.3-4_i386 +tk-dev_8.5.0-2.1_all +tk8.4-dev_8.4.19-5_i386 +tk8.5-dev_8.5.11-2_i386 +tqsllib-dev_2.2-5_i386 +trafficserver-dev_3.0.5-1_i386 +tuxpaint-dev_1:0.9.21-1.1_all +unagi-dev_0.3.3-2_i386 +unixodbc-dev_2.2.14p2-5_i386 +uthash-dev_1.9.5-1_all +uuid-dev_2.20.1-5.3_i386 +vdr-dev_1.7.28-1_all +vflib3-dev_3.6.14.dfsg-3+b1_i386 +voms-dev_2.0.8-1_i386 +vstream-client-dev_1.2-6.1_i386 +wcslib-dev_4.13.4-1_i386 +weechat-dev_0.3.8-1+deb7u1_all +weechat-dev_0.4.3-2~bpo70+1_all +wireshark-dev_1.10.7-1~bpo70+1_i386 +wireshark-dev_1.8.2-5wheezy10_i386 +witty-dev_3.2.1-2_all +wordnet-dev_1:3.0-29_i386 +wzdftpd-dev_0.8.3-6.2_i386 +x11proto-bigreqs-dev_1:1.1.2-1_all +x11proto-composite-dev_1:0.4.2-2_all +x11proto-core-dev_7.0.23-1_all +x11proto-damage-dev_1:1.2.1-2_all +x11proto-dmx-dev_1:2.3.1-2_all +x11proto-dri2-dev_2.6-2_all +x11proto-fixes-dev_1:5.0-2_all +x11proto-fonts-dev_2.1.2-1_all +x11proto-gl-dev_1.4.15-1_all +x11proto-input-dev_2.2-1_all +x11proto-kb-dev_1.0.6-2_all +x11proto-print-dev_1.0.5-2_all +x11proto-randr-dev_1.3.2-2_all +x11proto-record-dev_1.14.2-1_all +x11proto-render-dev_2:0.11.1-2_all +x11proto-resource-dev_1.2.0-3_all +x11proto-scrnsaver-dev_1.2.2-1_all +x11proto-video-dev_2.3.1-2_all +x11proto-xcmisc-dev_1.2.2-1_all +x11proto-xext-dev_7.2.1-1_all +x11proto-xf86bigfont-dev_1.2.0-3_all +x11proto-xf86dga-dev_2.1-3_all +x11proto-xf86dri-dev_2.1.1-2_all +x11proto-xf86vidmode-dev_2.3.1-2_all +x11proto-xinerama-dev_1.2.1-2_all +xaw3dg-dev_1.5+E-18.2_i386 +xbmc-addons-dev_2:13.1~rc1+dfsg1-1~bpo70+1_all +xbmc-eventclients-dev_2:11.0~git20120510.82388d5-1_all +xbmc-eventclients-dev_2:13.1~rc1+dfsg1-1~bpo70+1_all +xfce4-panel-dev_4.8.6-4_i386 +xfslibs-dev_3.1.7+b1_i386 +xmhtml1-dev_1.1.7-18_i386 +xmms2-dev_0.8+dfsg-4_all +xorg-dev_1:7.7+3~deb7u1_all +xotcl-dev_1.6.7-2_i386 +xpaint-dev_2.9.1.4-3+b2_i386 +xserver-xorg-dev_2:1.12.4-6+deb7u2_i386 +xserver-xorg-input-evdev-dev_1:2.7.0-1_all +xserver-xorg-input-joystick-dev_1:1.6.1-1_all +xserver-xorg-input-synaptics-dev_1.6.2-2_all +xtrans-dev_1.2.7-1_all +xulrunner-dev_24.4.0esr-1~deb7u2_i386 +xutils-dev_1:7.7~1_i386 +xviewg-dev_3.2p1.4-28.1_i386 +yate-dev_4.1.0-1~dfsg-3_i386 +yorick-dev_2.2.02+dfsg-6_i386 +zathura-dev_0.1.2-4_all +zlib1g-dev_1:1.2.7.dfsg-13_i386 +znc-dev_0.206-2_i386 +zsh-dev_4.3.17-1_i386 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold b/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t11_package/SearchPackage3Test_gold b/src/github.com/smira/aptly/system/t11_package/SearchPackage3Test_gold new file mode 100644 index 00000000..1fef093c --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/SearchPackage3Test_gold @@ -0,0 +1 @@ +nginx-full_1.2.1-2.2+wheezy2_amd64 diff --git a/src/github.com/smira/aptly/system/t11_package/SearchPackage4Test_gold b/src/github.com/smira/aptly/system/t11_package/SearchPackage4Test_gold new file mode 100644 index 00000000..ffe5c1be --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/SearchPackage4Test_gold @@ -0,0 +1,4 @@ + +coreutils_8.13-3.5_amd64 +coreutils_8.13-3.5_i386 +coreutils_8.13-3.5_source \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage1Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage1Test_gold new file mode 100644 index 00000000..2596fdce --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/ShowPackage1Test_gold @@ -0,0 +1,163 @@ + + + + + + + + + +Architecture: amd64 +Architecture: amd64 +Architecture: amd64 +Architecture: amd64 +Architecture: i386 +Architecture: i386 +Architecture: i386 +Architecture: i386 +Breaks: nginx (<< 1.4.5-1) +Breaks: nginx (<< 1.4.5-1) +Conflicts: nginx-full, nginx-light, nginx-naxsi +Conflicts: nginx-full, nginx-light, nginx-naxsi +Conflicts: nginx-full, nginx-light, nginx-naxsi +Conflicts: nginx-full, nginx-light, nginx-naxsi +Conflicts: nginx-full-dbg, nginx-light-dbg, nginx-naxsi-dbg +Conflicts: nginx-full-dbg, nginx-light-dbg, nginx-naxsi-dbg +Conflicts: nginx-full-dbg, nginx-light-dbg, nginx-naxsi-dbg +Conflicts: nginx-full-dbg, nginx-light-dbg, nginx-naxsi-dbg +Depends: nginx-common (= 1.2.1-2.2+wheezy2), perl (>= 5.14.2-21+deb7u1), perlapi-5.14.2, libc6 (>= 2.11), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), liblua5.1-0, libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libperl5.14 (>= 5.14.2), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Depends: nginx-common (= 1.2.1-2.2+wheezy2), perl (>= 5.14.2-21+deb7u1), perlapi-5.14.2, libc6 (>= 2.11), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), liblua5.1-0, libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libperl5.14 (>= 5.14.2), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Depends: nginx-common (= 1.6.0-1~bpo70+1), perl (>= 5.14.2-21+deb7u1), perlapi-5.14.2, libc6 (>= 2.11), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), liblua5.1-0, libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libperl5.14 (>= 5.14.2), libssl1.0.0 (>= 1.0.1), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.2.0) +Depends: nginx-common (= 1.6.0-1~bpo70+1), perl (>= 5.14.2-21+deb7u1), perlapi-5.14.2, libc6 (>= 2.11), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), liblua5.1-0, libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libperl5.14 (>= 5.14.2), libssl1.0.0 (>= 1.0.1), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.2.0) +Depends: nginx-extras (= 1.2.1-2.2+wheezy2) +Depends: nginx-extras (= 1.2.1-2.2+wheezy2) +Depends: nginx-extras (= 1.6.0-1~bpo70+1) +Depends: nginx-extras (= 1.6.0-1~bpo70+1) +Description-md5: 5b3494bcc5c46937a8b11b44469c77b3 +Description-md5: 5b3494bcc5c46937a8b11b44469c77b3 +Description-md5: a1852af8d0ef61a51df8e6cb9581d6a6 +Description-md5: a1852af8d0ef61a51df8e6cb9581d6a6 +Description-md5: dfe40e4b526a7896997294d525c6e22f +Description-md5: dfe40e4b526a7896997294d525c6e22f +Description-md5: dfe40e4b526a7896997294d525c6e22f +Description-md5: dfe40e4b526a7896997294d525c6e22f +Description: nginx web/proxy server (extended version) +Description: nginx web/proxy server (extended version) +Description: nginx web/proxy server (extended version) +Description: nginx web/proxy server (extended version) +Description: nginx web/proxy server (extended version) - debugging symbols +Description: nginx web/proxy server (extended version) - debugging symbols +Description: nginx web/proxy server (extended version) - debugging symbols +Description: nginx web/proxy server (extended version) - debugging symbols +Filename: nginx-extras-dbg_1.2.1-2.2+wheezy2_amd64.deb +Filename: nginx-extras-dbg_1.2.1-2.2+wheezy2_i386.deb +Filename: nginx-extras-dbg_1.6.0-1~bpo70+1_amd64.deb +Filename: nginx-extras-dbg_1.6.0-1~bpo70+1_i386.deb +Filename: nginx-extras_1.2.1-2.2+wheezy2_amd64.deb +Filename: nginx-extras_1.2.1-2.2+wheezy2_i386.deb +Filename: nginx-extras_1.6.0-1~bpo70+1_amd64.deb +Filename: nginx-extras_1.6.0-1~bpo70+1_i386.deb +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Homepage: http://nginx.net +Installed-Size: 1231 +Installed-Size: 1315 +Installed-Size: 1482 +Installed-Size: 1526 +Installed-Size: 5256 +Installed-Size: 5407 +Installed-Size: 8841 +Installed-Size: 9867 +MD5sum: 02a19018aae7a2a399f507fc0cb64d62 +MD5sum: 4b3b6c67272522433d50420d25e2b645 +MD5sum: 4e7009234d9b2d284b8986e7ffbe2846 +MD5sum: 6fee6f3675b1c56aa569f1cd655147c9 +MD5sum: c4a6097ed1e9cd3cc48c379acc444a1a +MD5sum: d2ebc412ca7dfcc2f9fa0993ad6ea21e +MD5sum: f7449a09f637555070687aa1adcdbebc +MD5sum: fabd2b985a687bbf3b6a3a29d7ef5619 +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Maintainer: Kartik Mistry +Package: nginx-extras +Package: nginx-extras +Package: nginx-extras +Package: nginx-extras +Package: nginx-extras-dbg +Package: nginx-extras-dbg +Package: nginx-extras-dbg +Package: nginx-extras-dbg +Priority: extra +Priority: extra +Priority: extra +Priority: extra +Priority: extra +Priority: extra +Priority: extra +Priority: extra +Provides: httpd, httpd-cgi, nginx +Provides: httpd, httpd-cgi, nginx +Provides: httpd, nginx +Provides: httpd, nginx +SHA1: 286a9e206c57dfd33ee318ed4f45c3619d3a68d4 +SHA1: 5ae182d975a5d249340b9cc24512ac92157ce170 +SHA1: 5dc4cd5bfa96083ad01a32ff83c11857304c3a8b +SHA1: 69f94693a5693d0e02d8f473ab0fb8628c26fdde +SHA1: 778e4cd89b4323f307ba0476488982fec1c30b34 +SHA1: 932ad607b2ce5e130071d403e62adf8c4e90e257 +SHA1: abd23b106ee886c5fab024433f1ef6db4699c282 +SHA1: fe254aca7d88d7b0f42ef5951ab4e4013e147958 +SHA256: 2b2901cd6fac19b06bd8750ce6dd337306e76fdf5ae27b5693702b41fb96ad1b +SHA256: 390323fef7da9a6465eb25139cde8bbeb3588398f1e40c31973921dfea32d8bf +SHA256: 8970a47653ed40d4b2096c2c42d7e3858fe8ac6b291c1bc6f3c52a535589d790 +SHA256: 9e38a151e26168a05b39e9f50ee12e621cf7fef44096dc0856b70f81205de24b +SHA256: a1cc9d55d5e17b6b0ab000a7b50a76dd175e83fe89742827b62ca36cd05042f6 +SHA256: b7bbee42030cf59a17a9fa28d9bc3aa07dd7e328a5bd002ac7fa3698c5456f1b +SHA256: bbb5c9abac66cc848b9b8ce8b6fabe1d9b3e826959afd6d8ec502b95d5f0c814 +SHA256: d970bbb7fb92bc13fa1447de0c04c9e31c45056a450ce173f75d4e5715d0850b +Section: debug +Section: debug +Section: debug +Section: debug +Section: httpd +Section: httpd +Section: httpd +Section: httpd +Size: 4450540 +Size: 4549842 +Size: 5181882 +Size: 5302224 +Size: 601422 +Size: 606894 +Size: 699448 +Size: 705372 +Source: nginx +Source: nginx +Source: nginx +Source: nginx +Source: nginx +Source: nginx +Source: nginx +Source: nginx +Suggests: nginx-doc (= 1.6.0-1~bpo70+1) +Suggests: nginx-doc (= 1.6.0-1~bpo70+1) +Tag: role::debug-symbols +Tag: role::debug-symbols +Version: 1.2.1-2.2+wheezy2 +Version: 1.2.1-2.2+wheezy2 +Version: 1.2.1-2.2+wheezy2 +Version: 1.2.1-2.2+wheezy2 +Version: 1.6.0-1~bpo70+1 +Version: 1.6.0-1~bpo70+1 +Version: 1.6.0-1~bpo70+1 +Version: 1.6.0-1~bpo70+1 \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage2Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage2Test_gold new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage3Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage3Test_gold new file mode 100644 index 00000000..d8eeb960 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/ShowPackage3Test_gold @@ -0,0 +1,21 @@ +Package: nginx-full +Version: 1.2.1-2.2+wheezy2 +Installed-Size: 915 +Priority: optional +Section: httpd +Maintainer: Kartik Mistry +Architecture: amd64 +Description: nginx web/proxy server (standard version) +MD5sum: 586a2ff5648004cd0114447f5df46a29 +SHA1: 7bf9b91714046f12d765adad860677f5b650c616 +SHA256: aa724376b6bf534c8ff38a8c5de4d4208d4de2b57946f875857349436542306b +Description-md5: b334eec6202adf5e9045cc6066a082d1 +Conflicts: nginx-extras, nginx-light, nginx-naxsi +Filename: nginx-full_1.2.1-2.2+wheezy2_amd64.deb +Size: 435328 +Homepage: http://nginx.net +Depends: nginx-common (= 1.2.1-2.2+wheezy2), libc6 (>= 2.10), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Provides: httpd, nginx +Source: nginx +Tag: network::server, protocol::http, role::program + diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage4Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage4Test_gold new file mode 100644 index 00000000..4d72b1df --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/ShowPackage4Test_gold @@ -0,0 +1,24 @@ +Package: nginx-full +Version: 1.2.1-2.2+wheezy2 +Installed-Size: 915 +Priority: optional +Section: httpd +Maintainer: Kartik Mistry +Architecture: amd64 +Description: nginx web/proxy server (standard version) +MD5sum: 586a2ff5648004cd0114447f5df46a29 +SHA1: 7bf9b91714046f12d765adad860677f5b650c616 +SHA256: aa724376b6bf534c8ff38a8c5de4d4208d4de2b57946f875857349436542306b +Conflicts: nginx-extras, nginx-light, nginx-naxsi +Description-md5: b334eec6202adf5e9045cc6066a082d1 +Filename: nginx-full_1.2.1-2.2+wheezy2_amd64.deb +Size: 435328 +Depends: nginx-common (= 1.2.1-2.2+wheezy2), libc6 (>= 2.10), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Homepage: http://nginx.net +Provides: httpd, nginx +Source: nginx +Tag: network::server, protocol::http, role::program + +Files in the pool: + ${HOME}/.aptly/pool/58/6a/nginx-full_1.2.1-2.2+wheezy2_amd64.deb + diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage5Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage5Test_gold new file mode 100644 index 00000000..a8f69479 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/ShowPackage5Test_gold @@ -0,0 +1,25 @@ +Package: nginx-full +Version: 1.2.1-2.2+wheezy2 +Installed-Size: 915 +Priority: optional +Section: httpd +Maintainer: Kartik Mistry +Architecture: amd64 +Description: nginx web/proxy server (standard version) +MD5sum: 586a2ff5648004cd0114447f5df46a29 +SHA1: 7bf9b91714046f12d765adad860677f5b650c616 +SHA256: aa724376b6bf534c8ff38a8c5de4d4208d4de2b57946f875857349436542306b +Homepage: http://nginx.net +Source: nginx +Size: 435328 +Conflicts: nginx-extras, nginx-light, nginx-naxsi +Description-md5: b334eec6202adf5e9045cc6066a082d1 +Tag: network::server, protocol::http, role::program +Filename: nginx-full_1.2.1-2.2+wheezy2_amd64.deb +Depends: nginx-common (= 1.2.1-2.2+wheezy2), libc6 (>= 2.10), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Provides: httpd, nginx + +References to package: + mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy + mirror [wheezy-main-src]: http://mirror.yandex.ru/debian/ wheezy [src] + diff --git a/src/github.com/smira/aptly/system/t11_package/ShowPackage6Test_gold b/src/github.com/smira/aptly/system/t11_package/ShowPackage6Test_gold new file mode 100644 index 00000000..122178d8 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/ShowPackage6Test_gold @@ -0,0 +1,29 @@ +Package: nginx-full +Version: 1.2.1-2.2+wheezy2 +Installed-Size: 915 +Priority: optional +Section: httpd +Maintainer: Kartik Mistry +Architecture: amd64 +Description: nginx web/proxy server (standard version) +MD5sum: 586a2ff5648004cd0114447f5df46a29 +SHA1: 7bf9b91714046f12d765adad860677f5b650c616 +SHA256: aa724376b6bf534c8ff38a8c5de4d4208d4de2b57946f875857349436542306b +Homepage: http://nginx.net +Tag: network::server, protocol::http, role::program +Conflicts: nginx-extras, nginx-light, nginx-naxsi +Source: nginx +Size: 435328 +Filename: nginx-full_1.2.1-2.2+wheezy2_amd64.deb +Provides: httpd, nginx +Depends: nginx-common (= 1.2.1-2.2+wheezy2), libc6 (>= 2.10), libexpat1 (>= 2.0.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libgeoip1 (>= 1.4.8+dfsg), libpam0g (>= 0.99.7.1), libpcre3 (>= 8.10), libssl1.0.0 (>= 1.0.0), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25), zlib1g (>= 1:1.1.4) +Description-md5: b334eec6202adf5e9045cc6066a082d1 + +References to package: + mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy + mirror [wheezy-main-src]: http://mirror.yandex.ru/debian/ wheezy [src] + local repo [repo1] + snapshot [snap1]: Snapshot from mirror [wheezy-main]: http://mirror.yandex.ru/debian/ wheezy + snapshot [snap4]: Merged from sources: 'snap1', 'snap2', 'snap3' + snapshot [snap3]: Snapshot from mirror [wheezy-main-src]: http://mirror.yandex.ru/debian/ wheezy [src] + diff --git a/src/github.com/smira/aptly/system/t11_package/__init__.py b/src/github.com/smira/aptly/system/t11_package/__init__.py new file mode 100644 index 00000000..73dc4f0c --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/__init__.py @@ -0,0 +1,6 @@ +""" +Testing package subcommands +""" + +from .search import * +from .show import * diff --git a/src/github.com/smira/aptly/system/t11_package/search.py b/src/github.com/smira/aptly/system/t11_package/search.py new file mode 100644 index 00000000..fee0d545 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/search.py @@ -0,0 +1,34 @@ +from lib import BaseTest + + +class SearchPackage1Test(BaseTest): + """ + search package: regular search + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package search '$$Architecture (i386), Name (% *-dev)'" + + +class SearchPackage2Test(BaseTest): + """ + search package: missing package + """ + runCmd = "aptly package search 'Name (package-xx)'" + + +class SearchPackage3Test(BaseTest): + """ + search package: by key + """ + fixtureDB = True + runCmd = "aptly package search nginx-full_1.2.1-2.2+wheezy2_amd64" + + +class SearchPackage4Test(BaseTest): + """ + search package: by dependency + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package search coreutils" diff --git a/src/github.com/smira/aptly/system/t11_package/show.py b/src/github.com/smira/aptly/system/t11_package/show.py new file mode 100644 index 00000000..e1365fa0 --- /dev/null +++ b/src/github.com/smira/aptly/system/t11_package/show.py @@ -0,0 +1,62 @@ +from lib import BaseTest + + +class ShowPackage1Test(BaseTest): + """ + show package: regular show + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package show 'Name (% nginx-extras*)'" + + +class ShowPackage2Test(BaseTest): + """ + show package: missing package + """ + runCmd = "aptly package show 'Name (package-xx)'" + + +class ShowPackage3Test(BaseTest): + """ + show package: by key + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package show nginx-full_1.2.1-2.2+wheezy2_amd64" + + +class ShowPackage4Test(BaseTest): + """ + show package: with files + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + gold_processor = BaseTest.expand_environ + runCmd = "aptly package show -with-files nginx-full_1.2.1-2.2+wheezy2_amd64" + + +class ShowPackage5Test(BaseTest): + """ + show package: with inclusion + """ + fixtureDB = True + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package show -with-references nginx-full_1.2.1-2.2+wheezy2_amd64" + + +class ShowPackage6Test(BaseTest): + """ + show package: with inclusion + more inclusions + """ + fixtureDB = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror wheezy-main", + "aptly snapshot create snap2 from mirror wheezy-contrib", + "aptly snapshot create snap3 from mirror wheezy-main-src", + "aptly snapshot merge snap4 snap1 snap2 snap3", + "aptly repo create repo1", + "aptly repo import wheezy-main repo1 nginx", + ] + outputMatchPrepare = lambda _, s: "\n".join(sorted(s.split("\n"))) + runCmd = "aptly package show -with-references nginx-full_1.2.1-2.2+wheezy2_amd64" diff --git a/src/github.com/smira/aptly/system/udebs/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb b/src/github.com/smira/aptly/system/udebs/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb new file mode 100644 index 0000000000000000000000000000000000000000..0e7f0635801f6d0210e63ec5d5fb82b54d5e976c GIT binary patch literal 11806 zcma)iRZtv2v?Xo{3=Y8w4#C~sJwR~R;0}Wg1Og#wg6jk)0Rq8YgUjF=7<_OYoPp)- zzI|J}Kl{3F-R`d2=X7<|>8`HZO)p{XZR;e3V`p#k(b}EU=A*l{H;|5wj+alAPnbtQ zSXh*fj_0NSzdbJxkC1=>9o>ulXR*TJFv~_HgC$wf5$64Ek^2h5r{T9i0^p zZ5$TTe-sqdFrWD959FWhFUWl9aRAAI zE?cJ;Pw+?Sv{ls=C67bOo33|XEw8|^xJ|!jQOj+Kh}?USPm)|1DW-XZb2|_Faq3!K z-f?^Tz#_|W)BT=cK4 zP;}Wyc%yvk`G#S6?7mNOrExK`b)J1StNOOIKi92(rV58-)`5GpfIahH*Jt3so$tId zcSES*lpu^LI_npiF>T6WeR45oSqO3H7ilE$QFU(A#b#u@N z3X}>al=C|7-5GIMr4S!Mq-oXUu;`=k|6lh6gd)-0^fn@~A|csX`&$2(9A32Y|5Fq% zFRuXq|J2m~rHByRK-2!Ty@n&S71ek0ggp2|3c7@%v97~ja+{1+OWtqwSk9O6K#96` z`^%jQ=}{+XTgjQFey_0zvX9-N)9y{9C!L{53$>xZRric^AwgBP$%PU>5szvZg6J zQ8%*;mUZl!D8Elh%fbh%hlbVFn~#LR6H%YtF;T~f+`ATO!G>vCi55EZCV$Rx`YfNI zWB+Qus-Hg9J}m^G)z-s#Rl6#_qYK<6Ej^$3`MDfkJy`~?`nmjgUZ||7&iGO>`3yR% z_f_S*?h@1KtPf%1ZIMOYe?5?vsZ`GzI~qE5YlZY1(~eNn=}(kWVQ4!BA$)|fU?en_ zXQ{%eJCRZDLI>CW5bHyVScZ$_M?8f>0w!{-6d}&PBdr+3X@57To&v3syX`H_(qk!( zXRa|eFO+Jzv*~(d(6Rw+zeo*(h_W?^6VTokEIZ}CR+3kPN?DvIO<1CD*gbiWZJAB5^V(Q76k6lPy_&=46#+$q09iMS+HwDlkyS zzWyppB=2p8o;ka(=^0j`Dv-GR>6^@vNbEq3iQrIcQdCdwQZ~O!z8;T4Dwe$jenNy0 zYBxT7r6Q_Nl%pC0Pe$F{{rKW|aY<2P#e+#WSWHY}rOka+r430ec1WIYtN8UmFAfhz z-e$y?Mt}k)#)^qpcBoF@%!u z{SPZ)uQl`^DsBvtl)7^&Kx1Q(<uWw>_X z%u}njd}nO7*J0Ep|T{$A`tjGo>FDE?#74fxG4MP-UBB}xSBxY ztr2oCs0zd+h^ilDjomW>WYHi9d-uySnD?Rb?s-kBmIABSC_KF2yqUy+j_WARnDMU> zNye!LE6>a~6`LBe(pR~bJ#r$|lipt*y(l#GDL@fqm%D3$0#njI3UagqT!A92g94#S z1@Z*9wSUNLUek%7R_E9gezOx)54Ai*EAb~h*wB_^%NYx>sd`2##L z?dp$1Uvy`F#yc}O-HWb8llpdI!F0)0`GHep15g8 z7b=5^=6WCF?ujTW1AC|-?vnj%DW0eZzP*!oIDBx2JW-~tE!*yxBkH3k^`0bU9#SB~ z!21t>n=c7$A)-NdD!>*Vt3`=u3uO9y8CmJT!z+y?6%@G&5q0YKp0UYO&nv8D2;M1 zZ~tj(x_eDBEbVuV@JeQr$8vq4{a`xDjwYw6X;TVf2kkpM=Dt}+)}kwMME+LHRUqCB zKx*3%$v;gmknMc9Z-lNg--;(@o37)v62tvST4In|Y|*%^OYeQG>#~NBOsBJf;G5)_ z!ygV!)2N?uO6GJM!*Rd4mw#8h;!8D^L4WgW!pP8JQ4q!YOThuUKCOJ!>X-B*b%jjy zW~Rpu?=C`JY9}p$(kYcMCFKRlw8w836a0Iv=#+4o_|jtMDyJ3r(?QlSYr@3(>=0Z&1sR%b6M~ zWb@~SGOqLwNvG$RS}9N{r-;u0oIK>VxNIh$)G$LA0hgw)#kJ?GbmjcaaK%P*V}c{W zV){k~kD`1B9)i5YDbv;=dn^Gd%PWVGE(^0B<@RqgQx7dQWv&JN=Kg*F<*g)0=jePm zb!Z3_*@HMLyPDQ8z~5E=nRH6#_`z9E@JpUrt~KJ%WxbO~t6`#en4vuX(hq`JOn-@N z)a7Eg$c>88ItLjXPk%B|Po%gu^L6)i7OspclZDs0(WXsVcvKF4i(n=lxDteHj z^hjp$Mz<#vi0kJwNS7i8sX`Txomak^w|%HYY9v@)YGNfYvOBdM1sM4#=hl3P#Z6w9 zq5I9Z@|NRG`l}F*A=j}AfZX4x2o$$XZ*$GRHoALcm9H%@c_qm4%70bPC+G1rRWvWy z{E5Iorf4x-lyO|a$)qA^ldC%J>}ZP^nVg)db4lxH^#9Chx{s774i_gZXtomhHG+3g z+l&8Y9e)w*SH;xO)E4bBe6h3c-5f^HMlm+DMf}Qo2}RFt%iJYE7hv_2gaq63^--p! z7vY$4s6)mVxavidZAa#`DzXWe_$@}3zdqz8N|;L7I?Pq&$b`q3;^67!*%j-@zSe##22Djnv zK5h{D?IQiyV#0&%L;SN!!91{w!qtg9J{8IN;N+taTArKtgu3LyQ33xPkMB3D zENat{3hQA`SJSt?3n+gmVK_Of^^=X(H5jsY^D0b@<~eV5KZGt^32pi7LEn;Vp$FAVdH@<4PA{MHj}%vV@UgE72xI-g^krNnOj7M?CTCX1e=Sl zTw$>e@CH-aNZdr4>+?)p8(q8R9Vz*$$QCTgGA6#Cb6~Y8`~GWvB{0xQ>{226)4xwu z3>Owhb>ofRM$NG-iIq4fXC!{y;^2{S6=VmIe>CW~KLvHpZNJSmPhw9$;KJLL3`q98 zsTAQuN@+1?QP~%LZ%pU+G%@|BvA!x2{h9khj`R1X;7~`vUHQDdq>?0R>8rrML}O#1 zG0T>d0FOx1aH}yTo?;)ivukI~Pvc=8+`md6PP}I;{~5T|Eu45S{O)F0;`{v*jI9MR zjry?9i%lb+Iq}Qw8llvp{CJHJ6=G=ocZXx@S{oTpA%^o2%zqh76dCz&`zf2&9T!6e zqdFii$M|=&Ys`~w-75dfPRjgCt|SU)t5LFZT>c05l~xJduGkIv1C6)-T{#xlJ9%3N z4Cpk!_Z2_oDm$*-0!S1U*+t^ewD-)L$e+&{A^l@&qxt;VX(3$WG&Gr_phhsn}EtioEWgdwD(P6|N3DNd`2~0WT4T**Y#%tmMNV9^931t@npx5YTMPJn_l%v+neu>2jmd*RQoTimeuMLw-Pp&y=E1{~FEmlsh;-z`W~!r}NxcxH`$6abM)%uMBulTMPW0`+e{EgN z3Q}C4w^K_l93rr1a}cL{0p=6SHPdasJB6sc?Yc=CdFeC@iK?Pz<JB#_tLUU}!QtUJa)`A)dwbh)`GOG)GqF6&LmVTPbDq4Rwn z{0n~s012NTY|tOG*UPkZUbTcWx_1HP^7!&Cd(6+@-e-di_*Yn*)1v`P$&? z^f3x*svomrQYXHvWtZPnZ6b_EfmK`nF6(@5yX_u43Hq@shjX-=ACB&BVR#bD_50dg zVYlO7&wUJ^Lm&4$sUc9{Xkk7&B=l}73E}*B>5&FHJhu$J@20jNmzHe58n(>ufc3&s zx%1yrn|>e6F%7#|ZbvWc)LD4wJ>-5It?uNh-r(LfYW%zgf0kVOXS?6B=j&fIscds^ zHZ^v81w2au-kN14&<$)4MLvI|kviS!kxs>w=1$7(0G^ekXPuh^&!!|o1}@hlTmL>OFE(%p#X zx7NZTnSOpniQ3w4_on`T6Mf9$(D6Iri!hK`HPRttqiS!%x3&TF=S|rgvEkEG+IXeM z=D5knuwl?va+mzcAZ5+DgTJs^?^d6T2o>&T3ih;&{$<4<8LGyff@ET<SU!&>Pkd(9t^X*1{ zea+R+Gf=_fi0tO%>X%_>6WD_#p{Z=p?coD31V93%*sfk=T*K$#><^gsocK6S$OZc4 zRSKW#*`1gK`WE?){|JcSe}Rwcw^3!_OL?zxyL`FcLVoi>-WkC<3Ammn4vN7N*wRg~O&rI}^ z8aY>FV1pJ4>9=(&joz6x6|9~bDp^+iQ{@5Aoxvjici4D;dn7-VCxfAj1L?)GKzwo3 zk!0=wYKf=sk9-Z)P-q+?I#5QIAAa@KF+VLqc1>s*o+nvWCJ&+W*@=Gx`yOCx7JxBRvNev@x8ave#!FGpYs-U_qr zEFA0GtNE3=L1@Tk-`s0qT6roS=QOTHgD2%ZAdt_dewa7n@n^a}OT?}m__ zC1Ky9Z%S~jbCaEEzq;i>dsKbFZse1l8RFb#pg$U*NO7){Vc)VLgA(?dP@YVtJaT+) z`wO8j$vGYUr6G8l>FZb)gdLR12PQ9SFb=N6^3M22t(Lhyklp8o3$d!EO-Wb!h zZTJK9(q7+ty`sFzwd3_}@uIxGXj7GVa`%eVv(S{DxnbFsXN$;f;6?GaYfu%RC14db z6Y=5*)5pt)+6aH9e1S_%X>Eu_c{yndybomR6)9#hBW`>N13&59vPXGgY3skg<%{y# z(^4VkQYMc%`a6pr#ffG39>Er}hJ99|55fV<-tzsD{%oljC^ZM}EyK5vxhgw@5Ll?Z zR6baiS})ZNe&?qH?TsxgLtO;hBW_f6@x9(H2bQJcON@-5Gy5ghr5H%?0td_AQoN8! zC!96BP(zc>Y<@jsVu1*)xm@RB)kB`@h#M@%3fCL|1#b~KJd*8ni`9{ z?1fn=%1{S^JaM9K>7n^_=5KkC>H%NSJ2tPe0{Z^wZULMDaSS%eL+u6@fJ2I#c2TfVQ$Ck`Ax}i;8kV9ai*jpcQrmNJPEQlD9i#QWR-C$)nvWbEcVnfKWJZz)5 zd3vSEupty!tMN4_d}__gj3H1NqY*5yRg|U(2{Vgi(nf1Kp|zuKZ^l|tlsMV@UJk$B zx8HaWxJvcTQcOX?$Sk!QTi0drHHidImU*7@0=>vefnmr@kETjroW~}N?RB^yFHNIR|FnUO)WIrx1?;iA;Y?yOxraU~+E zFT><^jqgi(jC3rEKqoFs%DdixKMR^(nF17=%$3{N3Z>Jhdr5YCPirg!9=Lx&Y+MFhLX$=|lc8Q9nQs_sBG+uZGVS|R>xCc~8( zbv7SMXEHR&$uHr2c)S)YmXaR^c06-Sl>US{{JU>BpMk}J`oS@>Wu1_mb{~0 zrgcIO%g-_DVu1wt2en!?B2-z_r0-4aO`Wqyn(Ir2mO^dcxH*a;5iK^RuCAp&y4_bN z+q2U*FoWRw3@i#JlXHx#`oziUD6n zPG}%p+DWi$A}4L08A4K)V0zDbqokfMK!cgyAH|F6Kb(|_SG$8Qe`pg(p%o> z-0g?4%A)EXG(8(k_*Qrr&PxvTEq!$_+cYLw)Y6 z22CmzZ;QLJ4L1H+Xvhiu84Om+(9#O38dSMg)9J!pARU}~^C4nrk+Nnw`A2uoBNOrD zX2!JPm{J`5r=Ap#qM*T^@qzrA%GVh|EaK_@J-<-RstGam3{2;->z)LIJecr%FAl~Q z&V`P7%~H#rFWl-fb*{Bw$3roUzLfhnh@T`0H-`J;?Urhux#*SjUuMghkFuWh%$nIOP~E2Sf~ z^4VGYApOkmgVFvX7CK)|BS4-^p*m3tn|3UVfd}`)Z%i}iM8^{JEG?g99nr#Yi=Y2y#-{BA9qpj1pDNfC^)zIN(T*l3WM zdoq*fo*I0zM`EXGQ892gCi4o~iHQ91hqER1+2ca_pY%9C~1ZDUIFhg?Mwg zz$Gg1Te5lh=PHIAe5+hnG5A*|Cn@Ne6crPWhPnqdahPE1DscBEa{rjvSLrD{bfBnr!WP9|(gRbRTpUR7p0zPl^Wfa^4Z+ z*JCvG?39*SD-4EvR(2@(l!lZ{%9hwdQOOh@Zg)E$96Ux0MXxmq{|N_0+EG8a z60F9C4{9>yQw2}7l8f!V3N+kg9CKHkj!v1Er>7Hz|La6Ag1SgJSUa3`6AB@uhC(Yd zS9X@z>f`*5ayvhH;GRU8Lw?#=o`=A>ExkMaj>u|=Z{ovpXfBNbk`KrOnOilWOlhx2 zRA;4y24ntwTZiIy<|n+bfKI;HajnN)6!X4E!Mgo8AJ2(Lk)f-!x6WGl80lKH;6kC* zoPzq(GzQ&I3H{12EAH*1r0FJpZ`$Otn!dzIoB%m=3N{!ngdjLx@DJ8O?vwi7SHuGT z$4%_yUxEz*NG%%*;f;YgszI%}ji%a6i1_}$Y=|vY&tZS&y}tTY-5*(dllItp|I9Vg zW1TQMir1vh?H6Mv5os_7EDKkMVoFI&KyT-Eo5kUR%Ch*d@<_mo_T(2RtuvdwMp1kg zs;wD@V2YhKcV(*VsxRE0od8Hilgcq&2P%v-NbgA8}jt!uUeZ1Z$3#2!jbu(Dyx!_TH7LFU{?Y^;pHlvxpBaemG!qB^+D4 zkS{;8c(WqLT~jxH0PSF|M5QXve6@gl=tVvGMKAdrX`8b(mu#WT`r)#CVU#Ba^njDu z1K7ct-J#^>=gZH;VyS^WH z8KXh0D(%DCQHx9Z_gmF7Q~E(~187YDo~FOkyGpteNKNC^8%4(B=s1@NvBU$2uv!P$ zNflQyjo$kv6vB z2@0Qc>WL5a?Yq(HS@&kAPxjAzkFB;Tl3i+l>8=YQZARLdPbXx{yW!^S2*;H_cF3&6 z1?@L=wfvJ6c-6H;`8R_mT>U0}K3#~LHT+E)?GqC3>2D($O8iCsCxTgq9~;z@p7qZR zluY14enG{R*rEyez|Co8^i15}*dC+n-yx7w%f+~nHs}(H(^d*HLs!5Xr-uXN%EF%v z;m3GXI(RxP=>S#&|EWvy#nZl$BeKOQM?Nn0wqeJg|7MoS*;vBeF?@Nrw#b;_CJbn} z2gT4|KbmVK7-N);B1g>mdNTZU;U|)`htTDj)sU z$(w=&d9#;=Z1zs^jy$e2`&B+DOy>m)(H+vLKqXTO>(8~0ghOVTgo}eSOILo_>EN*; zN1BQ9GBU}5V>dkJz9Bsik%m?bw!C9iEHutF8U3O^UA+#QpqfBd6QYZ;ofDIvaMaN~ zU%fIRda&}XF^sLzwwVINSrhRO(-TrGPwkNI^q#6J-fSu_h^XU7hY@OKkU?ceNn@Pz z)0IQcJxF?CpVQ;7f4IASJ4xmI_XeS)EDGrQ;Jbi zxTA{U2lC)!{}hy+Z$PyQBqo2Sxxv%DsmCo+|B|luLkZDQgunmbug94e;8}{QB>K4p z$aKF?hiKCvcv58U&@to{PIevD&nX5tV3#uT6ctm(8gf9SKyS}s#c-espN8aj>rcrV z=Q4$LWp-G(yJb1AZ7t+%Riod`-KOGS*{}(_sM!3NrTy?d%CV-na*a^y6Ct>Z38BkQ z`i4==ow0B13qp%_+Bzv|D#my3()Ko+Jb;`DjXL5qV!+z%?ZyFeI_Kn_g(w~H$E7#8 z{?nX#W1JHvIgK1gYil4wr){_}xjHCixOx3vYG{QX^xC`q@=;(LlO9BK5kn`9d8a#6 zVKHo%AN>zmG@nZvAj(zYo;75LwcVGzifUBkO$NZ!gjETfmkYS}Vak6FAxlEtm85m;*m(x~1tPjJ&L4Fl|< zbD{cIck;=DbRabEkX+cuA=>B=gm~7N$aaZi0nN5SGI$HnzX$OqUY(;S!8If`m%RBt z&7gRch4YyAXuvEFroYnH7h1JF*VhMfm~j^Mzq;jz{I+!D+w?*Ha@y-BxbB#9W*uUI zeCDGWXck+IDG|zLYpPh1_$*N-ywx>w7YAV~sD#4c%4`V9{vswf5&)T7aCh(M`M{(q z>tL1ifscz~nn(RD%_}E`y9x9T^R98by-W2qvta-3YRtzFqA;R$N{~xxHj$O#?s&{$^`HP?(79E6t_CLA*Bh-rykZ5Bk7T?G zIY)xK!GYb+;ZjWoy8FSC)B3t|`mz!viMzoLk~-eai+UT*G} zkYArd#=&xT*|hI42y)n|=yJ+zLNE82ZpOl_<~ImE&=ZX-Z$gg@7ek-p5BpPX1he0q zNA`nJXe~~w7CS=WgdH7($uNTQ0O@KN$?_!)7c#~grUNfcrL}v8rqj_+ja>|PGIFZ% zF)-Gg2U%*$&;6$Yx3%ccN&*(hcpN&q=$P zG<2z#EbLw!?Y{2fm1fZ1VITWbNL%MMCAZMYDLjB0?sz^}m4MNn)OX(o=#${)ZvfHv z!9@p!k>21P9&34tyq~%Sk8s0JDKlp>fYLNucXbk=Z!nLuM-&JLY0aZa{=(T~P`(H3 z?pPw}?9n}6dgzk->~T~=df?I#`1BS6UB5%mH$1y}_`L(fg;c}t{PUp`mspU~-MdJM zXB0@+*rlxmC>_Rq@ravmxei1JfzFplK}}DbLLC9NTK5mxCH(QK=Rvt$89|fqwIosM z9pC%zcYNogo!W(6!p36!U4(jVx9^8Ed`bE9W&?FYllFWQl-eXhK2LR~8HkwybtNh4 zd@;F~Pll;Lqy2lO-oA=t$HH1<_?{e6=BwqSMlFlw+O$m{&yniS9UF{WS97(R+{BxN zdat#2Z>c*5hPVebZA7p%$@H>Qw z9i`nqc*vMZ%eS>c>pj6jGg*3F6YT{1&SmE_2tOGst`T7a`&s#^yk&*5%NaLP(oCY! z@9j@KmEwZ=e+0xY4+sb{SGWs3Bv|s{*NS^$Q9KbBSr3}v>L%U8Gb9ZIi-$<89zPolySFEl=b`ax`zmA18E_&5WR`5C^+@P z*ya0p7-Wa-bZ#qiRD_0!o@UZ3rL6PGE4>alQU_J^hM2AOUfuym9)O*U+IL8h3>ceX zpar;0V$3Hg*x}&-Ji44+7N$T80*-7++`u-V1&90jOHaSJ9BH2JgAB&|ECGBBz=K2R z@U30`1${mgT=vME4-(UU7?*fKbZ;V zwkfprD)e5#eafZ3mlp(F3Idn zu>3nTmy5G4@Bxr@0PEBng6MwbT6=otnG2}exW7!A9_P&ZC`5SuvpsBnybkJ_>ZuI9`x{nn-noUV`>da# zhzBOUN88tl632&kai#b7KobL^NDsEwKm;`VMJ;Y(Lo82`%+LG~%xWKtrKg21js~~g rb5pUt5P>aGtZLA%{BHICIYs~LIQ?%lp7-F-NX{BsjW##7N zBqjZK{+AgeursqFm^nJQxj5P}yP3E!|MdC)G0*cqw~~??Bap=9{sdTJX5b zn-|sO86)P|Vs%e+h3N&|Uo6MTN5gFDMO=Sq5(R>JzA*Rd4P9;=K(@(aUK8HB`RdK} zqPqlBh3)cE{OflX`boEN6apgpiW$vxe-+j)U)c66v;#RFU*@aG5N5WW6IxXgh|yK# z%_7xz|J-J!nWVqvr~qxp{Yr?7E;JoAcrovTSwu}QPK0t`O51^nS^fd;8M-jj8UtLd z>#9uD>MH9wM*Py=p=eIQWpaWeM2ZWEg(SYspAWG0N0qu?Ffj$5&+rHgK1I14au5W^UqU@_*#;Pb>dlieh8s;QpVQ_`ek4hwMe5 zm3}I#qPEV~y6Hn!3hsbi+NL)3i;zY!w`4f_h*CZmPk^~4p?ZArFGoBh{LFY`wBm32 z;}NAyuFqfx6JRSe@ue^^)W(&^rs@`FPMMz$cUb<+^)DG&`DEL2PQNhRO(r_Ocz~x$nT399u0f&E3x& zN00iHzu(W4oJP2|Nz4>tq{!bk2R_TbKt);_t*ez0)u4yJeU|Bm&mCGTvzs6#Yml+h zbUh46;=e{@snHNmu;bc?P+tAIPR_~d`MvoT%U4Wa4BI_+R$HZ{DN)c^Y>k)CmnNvlVu7McE;&iEy&J@Q?iCx@7!YMUX1Y z5p_$n^@pcuW$qBo+Gm-U>6`z`&F%$*7*+;X5>`enLI=S{z;SNC+%0n0Ob1_mEl=X)Qsfi(%fsfVUIGq!f-Qw(w&Qx?O0`^ zpO>7>*+hN75hP1|;C1|9c%o5&^5-T;O{w$YE%gm*89>G7 zxZai@^m*9_T9OYmPzPp7wU^HR(8&oA0s1G`-ZXd|&a{inElh>}x_my*eZJtkUCY{F z9BXg*93p%{UcG#VigTp!{$3(b zAPj_i)Z_gUJ2m<_2qJ4)H!1f2tqvz<1H`p|?jG?9g#0`ac5DvRY;g?xV%t4|Eczr$ zgT2jR;sv;?3P$$zNxp*0X9B{50v9G-q2yWuqwmE)S4IAl!>EuxN)qn{a(NjAJ#U!{ z0qu{%co;DDUE-A798RPU4+#HR8|WY7pio<_>t+BPQqJ8SHczwiX`q^B3B zQ*XtMuZ`|-jZ-I8JlP>%1SXU8#1Qn(+~XK*yp2b znJ9L+SXgnjG;<5{EZyH1TO$LVhE!fmBR1mWv$tTY8YZc2;Fl6|H9xn%3SUH8i4`LR z8oufxlByVA$un4MCKhyEiE(1RR)Vz+C1W@QsRZNhntyVI133c_T!-&?&DDf*@>s2 z#({~H~H)67k3E3-E=zWr}>BGQXrzjQ%J7w z%hWG-mWQ>JCnJYVcH2IUF1tN%ren9Ys&fHW-x{6%n{;L0;1&n)#3@3KRM?%sdFc(1jsv%PmiRYrMXE8&Lwn4C!-T1Hk zOt_jf{KE`cZs8x_*%!to946}5i$Tn4r#%!JMP%#6N;im4{NsB=#9rrf0+5=xh+9`- zK;6kSZo`TX+Q8N{YQstlTJ0d3wsj!|q3i~;yD}a}gS9fxs*GD#(|Yt7#-=zNTj7a% zYgt;Jp}TRWA|6LexgZ+DruYb30i2rBrWlsi|F5v;x6M=dNwf(i`$xDV(*FR-BJQx9 z+b8NC?7s}_h%6<4n3 z56bDv|KrB!=>8k`p5*%vf+wX?Kx=<{C7;_1{>>+=utmrfr-~Ov2dRL~skO<@J~y(nM#>aoyOd zutc zr+yZMLEb&XLV9JQga$eVrTLQWk>vSNcMjA2RoHU_fG+E+WLpluVvQrrCons(dff3s zy+6)Be>$Pdek{6dd+k5gV;*oT%R1_k3}22b?EwzNMG8QRC2M(aNJ$%@tszl9zGqH| z^;`;p;P`aojp5`)5Ax94O5%_$#H29YN59b2XGrCj1tq42$F3d zH`V!le7AIA0CqmNKe;C>hx!SUH27(^zJmA;kin1Ip0bHcvw01<`~6Sn%!H`-5o;4B z=RJa4gE?noFo>Q=QIJilF9ytoO_}8tI}=Sttg)a>s6d?zDfu#!S|_!Cakn&?F7#o+ z5&lcWjY0Mg%3sCL-L-L0A_I7bGn-Se8wgn}zi8nP%TQ3P3??iTX1}cpASoYQMH#SI1o|lQl{z`dN9q@U&4~b zZ&S@L%_k2TnY!@K zfE%_Cgan0&%k=iVFbq3;4DJ0J_BGPQv|=DWv|}Pg_65;+1HDO9^p{exZp;k{OMsu> zy}%$S;1Q?kxtSomCz`IPkM?TVJZ^#qid`G(HeAT~Q`Y|%tzRmG1Y6p;n9e-u6hb0nH*i#V@e-o!bW55POhD=j=9{f!1MAokMrrv z*6q?~nj;>es=U|e>WE4ShT5=r*-io>EWe(C!H0q2m=L9V_}Z}KN| zSzbpFIk90x>^l|Oo%hXDCX3L`??JaS?;F18`kUft(v3M^AI=3i5!E^0aw<%MO5G&i zW#2bBMM}97)kLFFcYM`|kEd5Tkt4m@$`#J9QHzRwUA$b%haWnXMFS(e#nD1Akm$4h zwD3`Ux=R*mjcr0oy=TjnJjq|*%IUn(>l%E#q_t8=Kv0#qJ#cZqd`PV1DXUJ&DoG>+ z_+Hp24}^X<8CPGmz%_Edqo3Z3e-Dj?N{NcE9QH17|I2~G9Fp8WZWIg1w68)Kbv?)t zdSb@dg+U`-*G$9X4-vq@i@xK+eM373(bwHCubj9F@fD*^Mv7OOEy-R}3g*+m#4?WL zKQf7~?896}0kzrL;>jAWjuvFLjf0ch4vHr*PVsv@U$~M=&xTJqIv@owKwnY7aoJ6; zrYoI_ew`%2v(GR5o5uY_5tP2Q zMOi1AKA3x}xK`X))DNQGGPkin=CTo-&7#5NLGxyN71xC#%Xe8NI z9GaX<&dQiv4w}d@85CSp$Iulf=g!s_QH=7%qq2R-le4jZev|2?n5q}!uzjSub9|yK zg_3qxc`?O~Kdpr*tn%%rbccg4zJ@Q&o6F{s@l-WN#>HdE@DUIf)u<~DoZgvo_(Zdw zQWN_<)oQ0OUpE%@aTd;2DR1zrjA9}rxopfy5bvA~4$w)afNdQ1 zPkaGgA~1FUK?}P!X#jd+6Vv)~>?ToK^D$e_;)r(eCNBVQa7ypPI>#!sRxx6{zJcI} zyw@O@P4v%Gv96c?>K8jNye=z9h&a}4FIpC1Gr_<`etAJyhV;IO1g<+cW7M8|{`rCO zwgt&cyMQDa5Bcltc$9{~H^znaxMqGb3)OcFKXeJdnJi>5+(f5K2wmP(d(K4}1&X`$ zlNY-bXZ!|c05~W0Te3JtEfQN8cX*Jliy0(X(nCOM8%azg2_>BbweU^k-gpK$xXV4= zgYw_}1`LAN%b|G|@af^BsbRm|Opq1vL%NzPJ1WW@tEv}1U6i+w3d;3wMikg2gMCU_2Sy+b`{tHMmR71+QT)D{ z^>av39S4#rMv;hetb0t)Yq6tP9Sq^_7f-ZN{6z-h{w|eG>NH@ds&54MTBgf z$UuH&h$R93qCSwK1KC!D3f+K{!OElf7|{n<5>g3d;I(T5@b7wQSE>9A(Wmxtji|N> zS9ofpL-qP9jEp{BYJV+A%NuOU``iVmz7-`Gr-JMb1N&-t6Jtr7!6W!#^a*=Zvp;mR z9aQ#ZsT$>9ac?6p(|0}k7n;X6qN%JhJt4N6LhY9-=fHyZ$2QB5{e15@Xi`h_`Cn#- zRS<(m$}wcVZ`_Q}bKJl8NIHO0YCgWAwH}_;Fw|n%b~eO!ndE7A@cCl;^Zl`V0!}S9 zcm_%hc$*9a?bcO6w>FyOD^q9bW&y6w2xmIc0I9qX8WO3WJq`2tJH`Mma;=8g;3ONc zN@jgay5I_Uy0^s-ru4?-*h-%Qu2*0LfTc`uo;c^j(6Bcp^Nsbs7)_+{V&p+IdAtjT zln9NF87_~OKQ>*;l^EA8fqHI0;83bJB9AW*?yZdP7(3EBWoric!`Wv_`=(~aaB zOkmu*wgG_r@#ZSUP8aI^5dz)gsi^HaXi^Y&HT|+3&&y2WfmFElq52=z^K153EQ0Mp z07sqD=^T4a!vl}{$`FuHHGO8;i`OcFSa>Ux_$y*# z}YT7Ly0liY&x~>vwDqy(xETb$pP3_(a?at zGPHthosFW76`|U=89yzv~B_vKlI*H2Hr*?gacU>%+cO0xykS4Zw z_Lc^mprF)2b}-xWb2&sx*NnwYZK&AvuR3d^<$B#fb^J&h^IBo?LMh-Mj=d74#JT}T zxQf^7*~Aa>u8Hz|f$SfIQt7LMTg>&qdj(#+#PDF9ebuJ3Wdt=N4UI(?);xxQfH(sy zLH&1mR&S4DVnfUGFpc z9y#L+ovH0d{@fxn1ZE9M16PVp0%)lw`)rW5NrmDDUSGjGh!Z_7R!OK80qT}l4r}=4 zJ^pjBT|AyGo(`Oxm17$k%K9>q--f~d%3xetM@ApA(zW-bSX6JYWv;p-Ze){Cd{by1 z+u(>KUKHLE&I#hQV%R`TyfZIzgw1Gx_whL_IG@{(L}jTo;lCKV?U$vT{%E}RN+ewU z7T2#o1)<8D(p#?KN*eDDX=I{#tqP}*EU|z|MRnfiH-QBAU5%20QGE`S1C`TEbilR7_ zd^xhfUbrW8DFgVJ+;|rjzmDm>Q@hggYj+YVjC~Lx{d*TV2ND9(CnQm5W!4XgKhx&U zj7x+mY$jCNHlpDvg`~lnnZlwJ(GP~BZ{!wCbgG)S87{|vBpHg*u<=}#tSx42;nS%s zYE#zBe~+7$PFIm;g%&!WerNwpmAdF6-_apws`x98!o=@co>Qiz20M|2ZU5))X%sq9 z2xJ11l1ivCbgO*$G}t1v0xanm*c8urKqWEb7!@Ytztgz-z%w!bwXyB)7oq2rA?5Pk{XJ ztHVLA$DkQzrRN@wBKyu_cFoOipP4*4smsoc|( zlh6ORi)Wx35CtdP|eC*-*gqO6A!-e z5B*&=f76F~W0WSjzOyMqmAAo_gu5x6!~YuSL!slE@O)$OCV}22bJ%RMb&$P4GP9^R zrdpFejcxy~`etrXXZQl7m(cNdz(YN7v$f2&xGg~SY;?rUAKT@fwQYJb)pv|?@4tPL zFXMg#hSXbZoGwWjO--Xe9(d+#oOT}>9RyIp2O8L)%aHVpEKB!oS=tn{wEd$3t19D# zak*O;=3o@;A8XMu`>b~QSf7efg{*9fcl;)x5_|8tFhQq~iCMb~BKhnC#i$>}l_d5c zdo_Y9|HWl#0#FV-$<-U?l9$y~e%7vI{!^%mV(lBAMDim$^=!Fi$Arm< zlA8NGvDdxjmQZ|B=b;YUmU3``C;Hd4r`z$(kpt_1Jvi^QPHuC5EzZuBj+7ymIB)ff z@Z#fdihv;xu|w8Oo_H~3UaF&^ZF2L*7{BkqIRtjOxUp%k&y6ZI>zc`d{_C?3b^6}z z4;_*RrU1^KsKA}(`4(z3Yurv8|2g>ARZ;Z(g9sEP?;EV0RUN5bk3Uf-ww8RCroDJh znLCQOcYhoUUjXw#1ZaPlvAmDFs+;=gI7hc_LlD9B;dY6eEQd@wH=!bThSNk%J1>5J z<30HECdMH&ZYyE2zC({O5Pjqvcip{d?eCRd#yT4QRXChO+(VNR{r~2M`n(V|#|YVil-Jr?1J0!i3WRIGUA}ra7;*<_zwx6%HV2AtVoiS(=M+VjN9Vfc5rGU~{fV%CNK4>if z2g1FK?PrD{g>h1)33-iV5ovsg9y@;p7uKXzCk8DTCQtN9)R+ z-iNkUa|l+9RObT>x=Nviq?q^6FYRfP)%Rd`()mOC^%iJWQt!<+_Y?bm5!elMwpW+q05$)|YdK7A zc!1sK1_Ma$T7p! z5*f;@xtmD~#ypHdQ0&;?w3_yODGq{%$Ft0lprJna_HJZQn~ZUuZF3Mbw6=*@KWYS~ z0)-;%Eto`6w~R%BT0a#gT5dsTr#QbtW1JntBsNSx%r^z%w9tf_FP-6=xIJzQK?IZt z1SiDipvuq?s8O`cF1X7`cxn&*fogN)8r>t($pXxpuoN4FrS{dj@IE2>gjWH?`-PqF zNjPSK8fe(vyD;w>cD|HX2BZupJh4j(T%E3jnzElJiC1Z22Ej1kcvx6e^) zjZ9RdRs~_N*h-j>C=y3wO-d)qoQhSlYo$Bt%+^#me;nbi@Kwcf1g*P~2;-a}1(B7r)=%el5<3b;*29Fexje#f2?wNGliZ z@%^@cBIwzKBKlD#Nd4}7DD-k%gO8;Wf5Trb@NRI|fnVcsEzwX)=-5CmgpIJV(Z>Bj z90oTd8R^Qn<%%Od><=A&39)G1NvKTz!A)Si&BhnQF@IKRZ2g%%$k~CSJtu}3gh$z( z;pu#CI?BtDV92YjZI#XHM%BHi-R)AeZi(EO80#z!+?oH4dmJ^I)oV*ZwAbP%UfA9zEY zFa7P=2NwA3elht+0DlUB$A`ypd-fTdy@NCB%LGi8i4dhLIPW0TPtdhoAOQph-Lf6N zY&h9A?r(!?uEd%Z>Jk(HLvBLhTB@Uz#Jf2Ok9kki<{+d8`J~9B!76oK4L7xr$uO_Q@lfM+;7>$u}ewuAef9>ZD@b@#qW9e5$UQTbMF zM5~KB4LygRoL7d!6O4ZaZYp3LPUwr4Zc;(}ddbXKK3|k>Oj~W@^qgDz6o!!r03WR8 zCV_yh{Bm<{aOK55h^p3fP-Z|hdmfV`m|kO%MpCWTXSOLxquBMgVu6gtvB4 z!G4iK8^?uF9eqYaCIQK6W~+c}y@1w&FR2$kiC&r46Z#Oo zDNqSNgc{Kb-h94$l4uG7g%eS4R+Mm%d|a$BOj8OaT)zqDR)TjV6pJj&pOP57GJcs{ z=&qwULqm&y(&LJr0kX+tXq{k%m2&-4@G~e^;4(y?s)|)Gu+Vv%obKBq^d0TEJL9}B zsp+6(FdLh}+WvwS)Md!{t~=TgGo9#=@h#4d_c+8dsO47OtOe_U}X{l=S z#;Trz_?(~LWBahCj@h*&8O8}vxYNkGjTlCrt^b4BHXFhu7vg=f&Ryb(=(^X&;~;+5 zC9^M65!nXIIVQ5fP3DI=W|@1}1Y`%uOhVbsJFFO$T|eFGuNxSY9D#ALF>=864JmgJ ziDx{4r_*63Am0bp)k&s5Ur@FEJpp21Nu{zdODng8t4q>!713Ya^x{E^IM$P6M zw8T`+rDiW{6HMHMMa0LUd@|nEfn=Q3po2YYauFBj)YMGM&t&(^EY<=!0tX(ZEcca8 zwDk+=>!w{dEpxG)L+U1mtzkf}IbBfATx?3GIkKiyU_g0v?kaQU(39%c)N!K|j>C!i zx6v-<)|}RHaMqzsTBj!x-QcqkZ|)fRtnP#rhpiUpgX|TDHgL_G^~a+cv0%#w(TDIj zt1Ce>jPFCExedZ_Yu>*@U8Bh=X1F`}J}Y`ds|j@7S6tWShj#|VZ=PYq9O-OFoo3x%G^&+R1d^S7YC~8i5TL)__GF zv2V$>N3&Yo-Jihixa@~2`3;KbjH0<(dMCo`ZnU)qpkM^T3|wL6rOun;g>&}FhjYyh zICM5jH|&vd50_0bn&Dx^+y8pX8-ys4*CV?4%@zJpIp!9trr;Q9e)B${rcOGV-k(7NuHj03U%rad9`MhjX*u7BdN!oy%j64EQzXRr&FGR;#Z!$ z&^Y(~&%#CGQ}M-ZBEzmT7ElkN4T2-*EFb^SN8y3~O_y)P4m#BJ#EPeO$WSng{wGFG z)zQZfBG-na?#b$3H&sr&)t=r}Q%>@2qO#`)aZ3TQ*6JJ71NKq5JvNc9M%+80!Y92* zSz)qP}ur6m>&o5s#?&_1!PmDQuuj7H;?F$t4B@f#0NJ;p?` zP(GY6`NkS?W$A$wGr3-TUaH$7vNI$E#ynzw?rqyusPyE*+mkGQs$Ik>F`?f_eakCU z#+GQkJdlDtHBp?rOD*|qCTqF}ibO8RoewHSj2>2aYTnv=@O*3IZ_Mn`P5iCerW^H| zk}`hTPRGv#8BcJB9>_wK?LTCHcE}hBI3+2EETMjErkj0RCTz~iw&Der$|XBLL}DJ# x(pY(bWxIR#re)==@A_4z9k+RR%VqZ+*T33+{l7!N#RUjrTXT^b%nJ Date: Sat, 2 May 2015 12:18:01 +0200 Subject: [PATCH 2/2] Imported Upstream version 0.9.1 --- bash_completion.d/aptly | 576 ++++++ src/github.com/smira/aptly/.travis.yml | 18 +- src/github.com/smira/aptly/AUTHORS | 14 +- src/github.com/smira/aptly/Gomfile | 18 +- src/github.com/smira/aptly/Makefile | 10 +- src/github.com/smira/aptly/README.rst | 17 +- .../p/go.crypto/codereview.cfg | 2 - .../p/go.crypto/openpgp/keys_test.go | 41 - .../p/go.crypto/sha3/keccakf.go | 165 -- .../code.google.com/p/go.crypto/sha3/sha3.go | 213 -- .../p/go.crypto/sha3/sha3_test.go | 270 --- .../code.google.com/p/go.crypto/ssh/agent.go | 250 --- .../code.google.com/p/go.crypto/ssh/certs.go | 378 ---- .../p/go.crypto/ssh/certs_test.go | 55 - .../p/go.crypto/ssh/channel.go | 594 ------ .../code.google.com/p/go.crypto/ssh/cipher.go | 100 - .../p/go.crypto/ssh/cipher_test.go | 62 - .../code.google.com/p/go.crypto/ssh/client.go | 524 ----- .../p/go.crypto/ssh/client_auth.go | 509 ----- .../p/go.crypto/ssh/client_auth_test.go | 368 ---- .../code.google.com/p/go.crypto/ssh/common.go | 352 ---- .../p/go.crypto/ssh/common_test.go | 57 - .../p/go.crypto/ssh/keys_test.go | 214 -- .../code.google.com/p/go.crypto/ssh/server.go | 692 ------- .../p/go.crypto/ssh/server_terminal.go | 81 - .../p/go.crypto/ssh/terminal/util_linux.go | 12 - .../p/go.crypto/ssh/test/keys_test.go | 246 --- .../p/go.crypto/ssh/test/tcpip_test.go | 47 - .../p/go.crypto/ssh/transport.go | 426 ---- .../src/github.com/AlekSi/pointer/pointer.go | 53 + .../src/github.com/cheggaaa/pb/README.md | 6 + .../_vendor/src/github.com/cheggaaa/pb/pb.go | 129 +- .../src/github.com/cheggaaa/pb/pb_nix.go | 34 +- .../src/github.com/cheggaaa/pb/pb_solaris.go | 5 + .../src/github.com/cheggaaa/pb/pb_test.go | 17 +- .../src/github.com/cheggaaa/pb/pb_x.go | 46 + .../src/github.com/gin-gonic/gin/.gitignore | 2 + .../src/github.com/gin-gonic/gin/.travis.yml | 6 + .../src/github.com/gin-gonic/gin/AUTHORS.md | 174 ++ .../src/github.com/gin-gonic/gin/CHANGELOG.md | 64 + .../gin-gonic/gin/Godeps/Godeps.json | 10 + .../src/github.com/gin-gonic/gin/LICENSE | 21 + .../src/github.com/gin-gonic/gin/README.md | 536 +++++ .../src/github.com/gin-gonic/gin/auth.go | 94 + .../src/github.com/gin-gonic/gin/auth_test.go | 61 + .../gin-gonic/gin/binding/binding.go | 216 ++ .../src/github.com/gin-gonic/gin/context.go | 434 ++++ .../github.com/gin-gonic/gin/context_test.go | 483 +++++ .../github.com/gin-gonic/gin/deprecated.go | 47 + .../gin/examples/app-engine/README.md | 7 + .../gin/examples/app-engine/app.yaml | 8 + .../gin/examples/app-engine/hello.go | 23 + .../gin-gonic/gin/examples/example_basic.go | 56 + .../pluggable_renderer/example_pongo2.go | 58 + .../examples/pluggable_renderer/index.html | 12 + .../src/github.com/gin-gonic/gin/gin.go | 143 ++ .../src/github.com/gin-gonic/gin/gin_test.go | 206 ++ .../src/github.com/gin-gonic/gin/logger.go | 105 + .../src/github.com/gin-gonic/gin/mode.go | 63 + .../src/github.com/gin-gonic/gin/recovery.go | 98 + .../github.com/gin-gonic/gin/recovery_test.go | 56 + .../github.com/gin-gonic/gin/render/render.go | 123 ++ .../gin-gonic/gin/response_writer.go | 100 + .../github.com/gin-gonic/gin/routergroup.go | 148 ++ .../src/github.com/gin-gonic/gin/utils.go | 82 + .../julienschmidt/httprouter/.travis.yml | 6 + .../julienschmidt/httprouter/LICENSE | 24 + .../julienschmidt/httprouter/README.md | 234 +++ .../julienschmidt/httprouter/path.go | 123 ++ .../julienschmidt/httprouter/path_test.go | 92 + .../julienschmidt/httprouter/router.go | 317 +++ .../julienschmidt/httprouter/router_test.go | 329 +++ .../julienschmidt/httprouter/tree.go | 534 +++++ .../julienschmidt/httprouter/tree_test.go | 559 +++++ .../github.com/mattn/go-colorable/README.md | 42 + .../mattn/go-colorable/_example/main.go | 15 + .../mattn/go-colorable/colorable_others.go | 16 + .../mattn/go-colorable/colorable_windows.go | 594 ++++++ .../src/github.com/ncw/swift/.gitignore | 4 + .../src/github.com/ncw/swift/.travis.yml | 10 + .../_vendor/src/github.com/ncw/swift/COPYING | 20 + .../src/github.com/ncw/swift/README.md | 105 + .../_vendor/src/github.com/ncw/swift/auth.go | 279 +++ .../github.com/ncw/swift/compatibility_1_0.go | 28 + .../github.com/ncw/swift/compatibility_1_1.go | 24 + .../_vendor/src/github.com/ncw/swift/doc.go | 19 + .../src/github.com/ncw/swift/example_test.go | 97 + .../_vendor/src/github.com/ncw/swift/meta.go | 174 ++ .../src/github.com/ncw/swift/meta_test.go | 213 ++ .../src/github.com/ncw/swift/notes.txt | 55 + .../_vendor/src/github.com/ncw/swift/rs/rs.go | 83 + .../src/github.com/ncw/swift/rs/rs_test.go | 96 + .../_vendor/src/github.com/ncw/swift/swift.go | 1825 +++++++++++++++++ .../ncw/swift/swift_internal_test.go | 409 ++++ .../src/github.com/ncw/swift/swift_test.go | 1284 ++++++++++++ .../github.com/ncw/swift/swifttest/server.go | 806 ++++++++ .../github.com/ncw/swift/timeout_reader.go | 57 + .../ncw/swift/timeout_reader_test.go | 107 + .../github.com/ncw/swift/watchdog_reader.go | 34 + .../ncw/swift/watchdog_reader_test.go | 61 + .../syndtr/goleveldb/leveldb/batch.go | 228 +- .../syndtr/goleveldb/leveldb/batch_test.go | 26 +- .../goleveldb/leveldb/cache/cache_test.go | 4 +- .../syndtr/goleveldb/leveldb/config.go | 40 - .../syndtr/goleveldb/leveldb/corrupt_test.go | 60 +- .../github.com/syndtr/goleveldb/leveldb/db.go | 259 ++- .../syndtr/goleveldb/leveldb/db_compaction.go | 626 +++--- .../syndtr/goleveldb/leveldb/db_iter.go | 33 +- .../syndtr/goleveldb/leveldb/db_snapshot.go | 85 +- .../syndtr/goleveldb/leveldb/db_test.go | 843 +++++++- .../syndtr/goleveldb/leveldb/db_util.go | 11 +- .../syndtr/goleveldb/leveldb/db_write.go | 57 +- .../goleveldb/leveldb/{error.go => errors.go} | 24 +- .../syndtr/goleveldb/leveldb/errors/errors.go | 76 + .../syndtr/goleveldb/leveldb/external_test.go | 3 +- .../leveldb/iterator/indexed_iter.go | 37 +- .../leveldb/iterator/indexed_iter_test.go | 2 +- .../leveldb/iterator/iter_suite_test.go | 8 +- .../goleveldb/leveldb/iterator/merged_iter.go | 15 +- .../goleveldb/leveldb/journal/journal.go | 8 +- .../syndtr/goleveldb/leveldb/key.go | 133 +- .../syndtr/goleveldb/leveldb/key_test.go | 94 +- .../goleveldb/leveldb/leveldb_suite_test.go | 13 +- .../syndtr/goleveldb/leveldb/memdb/memdb.go | 4 +- .../leveldb/memdb/memdb_suite_test.go | 10 +- .../syndtr/goleveldb/leveldb/opt/options.go | 299 ++- .../syndtr/goleveldb/leveldb/options.go | 77 +- .../syndtr/goleveldb/leveldb/session.go | 208 +- .../goleveldb/leveldb/session_record.go | 191 +- .../goleveldb/leveldb/session_record_test.go | 18 +- .../syndtr/goleveldb/leveldb/session_util.go | 67 +- .../goleveldb/leveldb/storage/storage.go | 30 + .../syndtr/goleveldb/leveldb/storage_test.go | 162 +- .../syndtr/goleveldb/leveldb/table.go | 128 +- .../goleveldb/leveldb/table/block_test.go | 28 +- .../syndtr/goleveldb/leveldb/table/reader.go | 456 ++-- .../syndtr/goleveldb/leveldb/table/table.go | 6 +- .../leveldb/table/table_suite_test.go | 8 +- .../goleveldb/leveldb/table/table_test.go | 7 +- .../syndtr/goleveldb/leveldb/table/writer.go | 2 +- .../syndtr/goleveldb/leveldb/testutil/db.go | 8 +- .../goleveldb/leveldb/testutil/ginkgo.go | 21 + .../goleveldb/leveldb/testutil/kvtest.go | 53 +- .../goleveldb/leveldb/testutil/storage.go | 1 + .../syndtr/goleveldb/leveldb/testutil/util.go | 14 + .../syndtr/goleveldb/leveldb/testutil_test.go | 4 + .../syndtr/goleveldb/leveldb/util.go | 4 +- .../goleveldb/leveldb/util/buffer_pool.go | 49 +- .../syndtr/goleveldb/leveldb/util/util.go | 1 - .../syndtr/goleveldb/leveldb/version.go | 142 +- .../src/github.com/syndtr/gosnappy/.hgignore | 30 + .../src/github.com/syndtr/gosnappy/AUTHORS | 12 + .../github.com/syndtr/gosnappy/CONTRIBUTORS | 34 + .../src/github.com/syndtr/gosnappy/LICENSE | 27 + .../src/github.com/syndtr/gosnappy/README | 11 + .../gosnappy/lib/codereview/codereview.cfg | 1 + .../syndtr/gosnappy/snappy/decode.go | 124 ++ .../syndtr/gosnappy/snappy/encode.go | 174 ++ .../syndtr/gosnappy/snappy/snappy.go | 38 + .../syndtr/gosnappy/snappy/snappy_test.go | 261 +++ .../x/crypto/.gitignore} | 1 - .../go.crypto => golang.org/x/crypto}/AUTHORS | 0 .../x/crypto}/CONTRIBUTORS | 0 .../go.crypto => golang.org/x/crypto}/LICENSE | 0 .../go.crypto => golang.org/x/crypto}/PATENTS | 0 .../go.crypto => golang.org/x/crypto}/README | 0 .../x/crypto}/bcrypt/base64.go | 0 .../x/crypto}/bcrypt/bcrypt.go | 4 +- .../x/crypto}/bcrypt/bcrypt_test.go | 9 + .../x/crypto}/blowfish/block.go | 95 +- .../x/crypto}/blowfish/blowfish_test.go | 90 +- .../x/crypto}/blowfish/cipher.go | 21 +- .../x/crypto}/blowfish/const.go | 0 .../x/crypto}/bn256/bn256.go | 2 +- .../x/crypto}/bn256/bn256_test.go | 0 .../x/crypto}/bn256/constants.go | 0 .../x/crypto}/bn256/curve.go | 0 .../x/crypto}/bn256/example_test.go | 0 .../x/crypto}/bn256/gfp12.go | 0 .../x/crypto}/bn256/gfp2.go | 0 .../x/crypto}/bn256/gfp6.go | 0 .../x/crypto}/bn256/optate.go | 0 .../x/crypto}/bn256/twist.go | 0 .../x/crypto}/cast5/cast5.go | 2 +- .../x/crypto}/cast5/cast5_test.go | 0 .../x/crypto}/curve25519/const_amd64.s | 8 +- .../x/crypto}/curve25519/cswap_amd64.s | 0 .../x/crypto}/curve25519/curve25519.go | 0 .../x/crypto}/curve25519/curve25519_test.go | 0 .../x/crypto}/curve25519/doc.go | 2 +- .../x/crypto}/curve25519/freeze_amd64.s | 0 .../x/crypto}/curve25519/ladderstep_amd64.s | 0 .../x/crypto}/curve25519/mont25519_amd64.go | 0 .../x/crypto}/curve25519/mul_amd64.s | 0 .../x/crypto}/curve25519/square_amd64.s | 0 .../x/crypto}/hkdf/example_test.go | 2 +- .../x/crypto}/hkdf/hkdf.go | 10 +- .../x/crypto}/hkdf/hkdf_test.go | 0 .../x/crypto}/md4/md4.go | 2 +- .../x/crypto}/md4/md4_test.go | 0 .../x/crypto}/md4/md4block.go | 0 .../x/crypto}/nacl/box/box.go | 8 +- .../x/crypto}/nacl/box/box_test.go | 2 +- .../x/crypto}/nacl/secretbox/secretbox.go | 6 +- .../crypto}/nacl/secretbox/secretbox_test.go | 0 .../x/crypto}/ocsp/ocsp.go | 2 +- .../x/crypto}/ocsp/ocsp_test.go | 0 .../x/crypto}/openpgp/armor/armor.go | 4 +- .../x/crypto}/openpgp/armor/armor_test.go | 0 .../x/crypto}/openpgp/armor/encode.go | 0 .../x/crypto}/openpgp/canonical_text.go | 0 .../x/crypto}/openpgp/canonical_text_test.go | 0 .../x/crypto}/openpgp/clearsign/clearsign.go | 18 +- .../openpgp/clearsign/clearsign_test.go | 42 +- .../x/crypto}/openpgp/elgamal/elgamal.go | 2 +- .../x/crypto}/openpgp/elgamal/elgamal_test.go | 0 .../x/crypto}/openpgp/errors/errors.go | 10 +- .../x/crypto}/openpgp/keys.go | 85 +- .../golang.org/x/crypto/openpgp/keys_test.go | 216 ++ .../x/crypto}/openpgp/packet/compressed.go | 2 +- .../crypto}/openpgp/packet/compressed_test.go | 0 .../x/crypto}/openpgp/packet/config.go | 18 + .../x/crypto}/openpgp/packet/encrypted_key.go | 4 +- .../openpgp/packet/encrypted_key_test.go | 0 .../x/crypto}/openpgp/packet/literal.go | 0 .../x/crypto}/openpgp/packet/ocfb.go | 0 .../x/crypto}/openpgp/packet/ocfb_test.go | 0 .../openpgp/packet/one_pass_signature.go | 4 +- .../x/crypto}/openpgp/packet/opaque.go | 2 +- .../x/crypto}/openpgp/packet/opaque_test.go | 0 .../x/crypto}/openpgp/packet/packet.go | 23 +- .../x/crypto}/openpgp/packet/packet_test.go | 2 +- .../x/crypto}/openpgp/packet/private_key.go | 10 +- .../openpgp/packet/private_key_test.go | 6 + .../x/crypto}/openpgp/packet/public_key.go | 39 +- .../crypto}/openpgp/packet/public_key_test.go | 2 +- .../x/crypto}/openpgp/packet/public_key_v3.go | 9 +- .../openpgp/packet/public_key_v3_test.go | 0 .../x/crypto}/openpgp/packet/reader.go | 2 +- .../x/crypto}/openpgp/packet/signature.go | 48 +- .../crypto}/openpgp/packet/signature_test.go | 0 .../x/crypto}/openpgp/packet/signature_v3.go | 4 +- .../openpgp/packet/signature_v3_test.go | 2 +- .../openpgp/packet/symmetric_key_encrypted.go | 7 +- .../packet/symmetric_key_encrypted_test.go | 0 .../openpgp/packet/symmetrically_encrypted.go | 2 +- .../packet/symmetrically_encrypted_test.go | 2 +- .../x/crypto}/openpgp/packet/userattribute.go | 0 .../openpgp/packet/userattribute_test.go | 0 .../x/crypto}/openpgp/packet/userid.go | 0 .../x/crypto}/openpgp/packet/userid_test.go | 0 .../x/crypto}/openpgp/read.go | 42 +- .../x/crypto}/openpgp/read_test.go | 43 +- .../x/crypto}/openpgp/s2k/s2k.go | 101 +- .../x/crypto}/openpgp/s2k/s2k_test.go | 21 +- .../x/crypto}/openpgp/write.go | 8 +- .../x/crypto}/openpgp/write_test.go | 0 .../x/crypto}/otr/libotr_test_helper.c | 0 .../x/crypto}/otr/otr.go | 2 +- .../x/crypto}/otr/otr_test.go | 0 .../x/crypto}/otr/smp.go | 0 .../x/crypto}/pbkdf2/pbkdf2.go | 2 +- .../x/crypto}/pbkdf2/pbkdf2_test.go | 0 .../x/crypto}/poly1305/const_amd64.s | 36 +- .../x/crypto}/poly1305/poly1305.go | 2 +- .../x/crypto}/poly1305/poly1305_amd64.s | 0 .../x/crypto}/poly1305/poly1305_test.go | 0 .../x/crypto}/poly1305/sum_amd64.go | 0 .../x/crypto}/poly1305/sum_ref.go | 0 .../x/crypto}/ripemd160/ripemd160.go | 2 +- .../x/crypto}/ripemd160/ripemd160_test.go | 0 .../x/crypto}/ripemd160/ripemd160block.go | 0 .../x/crypto}/salsa20/salsa/hsalsa20.go | 2 +- .../x/crypto}/salsa20/salsa/salsa2020_amd64.s | 0 .../x/crypto}/salsa20/salsa/salsa208.go | 0 .../x/crypto}/salsa20/salsa/salsa20_amd64.go | 0 .../x/crypto}/salsa20/salsa/salsa20_ref.go | 0 .../x/crypto}/salsa20/salsa/salsa_test.go | 0 .../x/crypto}/salsa20/salsa20.go | 4 +- .../x/crypto}/salsa20/salsa20_test.go | 0 .../x/crypto}/scrypt/scrypt.go | 4 +- .../x/crypto}/scrypt/scrypt_test.go | 0 .../src/golang.org/x/crypto/sha3/doc.go | 68 + .../src/golang.org/x/crypto/sha3/hashes.go | 65 + .../x/crypto/sha3/keccakKats.json.deflate | Bin 0 -> 521342 bytes .../src/golang.org/x/crypto/sha3/keccakf.go | 410 ++++ .../src/golang.org/x/crypto/sha3/register.go | 18 + .../src/golang.org/x/crypto/sha3/sha3.go | 226 ++ .../src/golang.org/x/crypto/sha3/sha3_test.go | 249 +++ .../src/golang.org/x/crypto/sha3/shake.go | 60 + .../golang.org/x/crypto/ssh/agent/client.go | 563 +++++ .../x/crypto/ssh/agent/client_test.go | 278 +++ .../golang.org/x/crypto/ssh/agent/forward.go | 103 + .../golang.org/x/crypto/ssh/agent/keyring.go | 183 ++ .../golang.org/x/crypto/ssh/agent/server.go | 209 ++ .../x/crypto/ssh/agent/server_test.go | 77 + .../x/crypto/ssh/agent/testdata_test.go | 64 + .../golang.org/x/crypto/ssh/benchmark_test.go | 122 ++ .../x/crypto}/ssh/buffer.go | 12 +- .../x/crypto}/ssh/buffer_test.go | 18 +- .../src/golang.org/x/crypto/ssh/certs.go | 474 +++++ .../src/golang.org/x/crypto/ssh/certs_test.go | 156 ++ .../src/golang.org/x/crypto/ssh/channel.go | 631 ++++++ .../src/golang.org/x/crypto/ssh/cipher.go | 344 ++++ .../golang.org/x/crypto/ssh/cipher_test.go | 59 + .../src/golang.org/x/crypto/ssh/client.go | 202 ++ .../golang.org/x/crypto/ssh/client_auth.go | 441 ++++ .../x/crypto/ssh/client_auth_test.go | 393 ++++ .../x/crypto}/ssh/client_test.go | 7 +- .../src/golang.org/x/crypto/ssh/common.go | 357 ++++ .../src/golang.org/x/crypto/ssh/connection.go | 144 ++ .../x/crypto}/ssh/doc.go | 3 +- .../x/crypto}/ssh/example_test.go | 95 +- .../src/golang.org/x/crypto/ssh/handshake.go | 393 ++++ .../golang.org/x/crypto/ssh/handshake_test.go | 311 +++ .../x/crypto}/ssh/kex.go | 24 +- .../x/crypto}/ssh/kex_test.go | 2 +- .../x/crypto}/ssh/keys.go | 371 ++-- .../src/golang.org/x/crypto/ssh/keys_test.go | 306 +++ .../x/crypto}/ssh/mac.go | 5 - .../x/crypto}/ssh/mempipe_test.go | 22 +- .../x/crypto}/ssh/messages.go | 317 +-- .../x/crypto}/ssh/messages_test.go | 138 +- .../src/golang.org/x/crypto/ssh/mux.go | 356 ++++ .../src/golang.org/x/crypto/ssh/mux_test.go | 525 +++++ .../src/golang.org/x/crypto/ssh/server.go | 477 +++++ .../x/crypto}/ssh/session.go | 309 ++- .../x/crypto}/ssh/session_test.go | 417 ++-- .../x/crypto}/ssh/tcpip.go | 195 +- .../x/crypto}/ssh/tcpip_test.go | 4 + .../x/crypto}/ssh/terminal/terminal.go | 315 ++- .../x/crypto}/ssh/terminal/terminal_test.go | 60 + .../x/crypto}/ssh/terminal/util.go | 4 +- .../x/crypto}/ssh/terminal/util_bsd.go | 2 +- .../x/crypto/ssh/terminal/util_linux.go | 11 + .../x/crypto/ssh/terminal/util_windows.go | 174 ++ .../x/crypto/ssh/test/agent_unix_test.go | 50 + .../golang.org/x/crypto/ssh/test/cert_test.go | 47 + .../x/crypto}/ssh/test/doc.go | 2 +- .../x/crypto}/ssh/test/forward_unix_test.go | 2 +- .../x/crypto}/ssh/test/session_test.go | 146 +- .../x/crypto/ssh/test/tcpip_test.go | 46 + .../x/crypto}/ssh/test/test_unix_test.go | 145 +- .../x/crypto/ssh/test/testdata_test.go | 64 + .../golang.org/x/crypto/ssh/testdata/doc.go | 8 + .../golang.org/x/crypto/ssh/testdata/keys.go | 43 + .../golang.org/x/crypto/ssh/testdata_test.go | 63 + .../src/golang.org/x/crypto/ssh/transport.go | 327 +++ .../x/crypto}/ssh/transport_test.go | 40 + .../x/crypto}/twofish/twofish.go | 2 +- .../x/crypto}/twofish/twofish_test.go | 0 .../x/crypto}/xtea/block.go | 0 .../x/crypto}/xtea/cipher.go | 2 +- .../x/crypto}/xtea/xtea_test.go | 0 .../x/crypto}/xts/xts.go | 2 +- .../x/crypto}/xts/xts_test.go | 0 src/github.com/smira/aptly/api/api.go | 113 + src/github.com/smira/aptly/api/files.go | 185 ++ src/github.com/smira/aptly/api/graph.go | 75 + src/github.com/smira/aptly/api/packages.go | 16 + src/github.com/smira/aptly/api/publish.go | 336 +++ src/github.com/smira/aptly/api/repos.go | 370 ++++ src/github.com/smira/aptly/api/router.go | 82 + src/github.com/smira/aptly/api/snapshot.go | 410 ++++ src/github.com/smira/aptly/aptly/report.go | 67 + src/github.com/smira/aptly/aptly/version.go | 2 +- src/github.com/smira/aptly/cmd/api.go | 15 + src/github.com/smira/aptly/cmd/api_serve.go | 52 + src/github.com/smira/aptly/cmd/cmd.go | 5 +- src/github.com/smira/aptly/cmd/config.go | 15 + src/github.com/smira/aptly/cmd/config_show.go | 38 + src/github.com/smira/aptly/cmd/context.go | 358 +--- src/github.com/smira/aptly/cmd/db_cleanup.go | 26 +- src/github.com/smira/aptly/cmd/graph.go | 120 +- .../smira/aptly/cmd/mirror_create.go | 12 +- src/github.com/smira/aptly/cmd/mirror_drop.go | 2 +- src/github.com/smira/aptly/cmd/mirror_edit.go | 4 +- src/github.com/smira/aptly/cmd/mirror_list.go | 2 + src/github.com/smira/aptly/cmd/mirror_show.go | 2 +- .../smira/aptly/cmd/mirror_update.go | 6 +- .../smira/aptly/cmd/package_search.go | 4 + .../smira/aptly/cmd/package_show.go | 8 +- src/github.com/smira/aptly/cmd/publish.go | 16 +- .../smira/aptly/cmd/publish_drop.go | 3 +- .../smira/aptly/cmd/publish_list.go | 2 + .../smira/aptly/cmd/publish_repo.go | 1 + .../smira/aptly/cmd/publish_snapshot.go | 11 +- .../smira/aptly/cmd/publish_switch.go | 23 +- .../smira/aptly/cmd/publish_update.go | 7 +- src/github.com/smira/aptly/cmd/repo_add.go | 157 +- src/github.com/smira/aptly/cmd/repo_create.go | 6 +- src/github.com/smira/aptly/cmd/repo_drop.go | 2 +- src/github.com/smira/aptly/cmd/repo_edit.go | 12 +- src/github.com/smira/aptly/cmd/repo_list.go | 2 + src/github.com/smira/aptly/cmd/repo_move.go | 4 +- src/github.com/smira/aptly/cmd/repo_remove.go | 2 +- src/github.com/smira/aptly/cmd/repo_show.go | 2 +- src/github.com/smira/aptly/cmd/run.go | 9 +- src/github.com/smira/aptly/cmd/serve.go | 2 +- .../smira/aptly/cmd/snapshot_diff.go | 2 +- .../smira/aptly/cmd/snapshot_drop.go | 2 +- .../smira/aptly/cmd/snapshot_filter.go | 2 +- .../smira/aptly/cmd/snapshot_list.go | 74 +- .../smira/aptly/cmd/snapshot_merge.go | 16 +- .../smira/aptly/cmd/snapshot_pull.go | 10 +- .../smira/aptly/cmd/snapshot_search.go | 2 +- .../smira/aptly/cmd/snapshot_show.go | 2 +- src/github.com/smira/aptly/cmd/task_run.go | 26 +- .../smira/aptly/console/progress.go | 13 +- .../smira/aptly/console/terminal.go | 4 +- .../smira/aptly/console/terminal_bsd.go | 10 - src/github.com/smira/aptly/context/context.go | 490 +++++ .../smira/aptly/database/leveldb.go | 2 +- .../smira/aptly/database/leveldb_test.go | 3 +- src/github.com/smira/aptly/deb/collections.go | 31 +- src/github.com/smira/aptly/deb/deb.go | 2 +- src/github.com/smira/aptly/deb/deb_test.go | 3 +- src/github.com/smira/aptly/deb/debian_test.go | 3 +- src/github.com/smira/aptly/deb/format.go | 85 +- src/github.com/smira/aptly/deb/format_test.go | 5 +- src/github.com/smira/aptly/deb/graph.go | 120 ++ src/github.com/smira/aptly/deb/import.go | 163 ++ src/github.com/smira/aptly/deb/index_files.go | 10 +- src/github.com/smira/aptly/deb/list.go | 20 +- src/github.com/smira/aptly/deb/list_test.go | 61 +- src/github.com/smira/aptly/deb/local.go | 9 +- src/github.com/smira/aptly/deb/local_test.go | 3 +- src/github.com/smira/aptly/deb/package.go | 30 +- .../aptly/deb/package_collection_test.go | 3 +- .../smira/aptly/deb/package_files_test.go | 3 +- .../smira/aptly/deb/package_test.go | 5 +- src/github.com/smira/aptly/deb/ppa_test.go | 3 +- src/github.com/smira/aptly/deb/publish.go | 74 +- .../smira/aptly/deb/publish_test.go | 6 +- src/github.com/smira/aptly/deb/reflist.go | 75 +- .../smira/aptly/deb/reflist_test.go | 100 +- src/github.com/smira/aptly/deb/remote.go | 32 +- src/github.com/smira/aptly/deb/remote_test.go | 29 +- src/github.com/smira/aptly/deb/snapshot.go | 89 +- .../smira/aptly/deb/snapshot_test.go | 3 +- .../smira/aptly/deb/version_test.go | 2 +- .../smira/aptly/files/files_test.go | 3 +- .../smira/aptly/files/package_pool.go | 16 +- .../smira/aptly/files/package_pool_test.go | 3 +- .../smira/aptly/files/public_test.go | 3 +- src/github.com/smira/aptly/http/download.go | 34 +- .../smira/aptly/http/download_test.go | 33 +- src/github.com/smira/aptly/http/http_test.go | 3 +- src/github.com/smira/aptly/man/aptly.1 | 119 +- .../smira/aptly/man/aptly.1.ronn.tmpl | 48 +- src/github.com/smira/aptly/query/lex_test.go | 3 +- .../smira/aptly/query/query_test.go | 3 +- .../smira/aptly/query/syntax_test.go | 3 +- src/github.com/smira/aptly/s3/public_test.go | 3 +- src/github.com/smira/aptly/s3/s3_test.go | 3 +- src/github.com/smira/aptly/swift/public.go | 229 +++ .../smira/aptly/swift/public_test.go | 187 ++ src/github.com/smira/aptly/swift/swift.go | 2 + .../smira/aptly/swift/swift_test.go | 12 + src/github.com/smira/aptly/system/api_lib.py | 91 + src/github.com/smira/aptly/system/lib.py | 63 +- src/github.com/smira/aptly/system/run.py | 25 +- src/github.com/smira/aptly/system/s3_lib.py | 4 +- .../smira/aptly/system/swift_lib.py | 87 + .../aptly/system/t01_version/VersionTest_gold | 2 +- .../system/t02_config/ConfigShowTest_gold | 17 + .../system/t02_config/CreateConfigTest_gold | 3 +- .../smira/aptly/system/t02_config/__init__.py | 8 + .../smira/aptly/system/t03_help/MainTest_gold | 3 + .../system/t03_help/MirrorCreateHelpTest_gold | 1 + .../system/t03_help/MirrorCreateTest_gold | 1 + .../aptly/system/t03_help/WrongFlagTest_gold | 1 + .../t04_mirror/CreateMirror13Test_mirror_show | 6 +- .../t04_mirror/CreateMirror17Test_mirror_show | 6 +- .../t04_mirror/CreateMirror1Test_mirror_show | 6 +- .../t04_mirror/CreateMirror21Test_mirror_show | 2 +- .../t04_mirror/CreateMirror25Test_mirror_show | 6 +- .../system/t04_mirror/CreateMirror27Test_gold | 4 + .../t04_mirror/CreateMirror27Test_mirror_show | 19 + .../system/t04_mirror/CreateMirror28Test_gold | 4 + .../t04_mirror/CreateMirror28Test_mirror_show | 20 + .../t04_mirror/CreateMirror2Test_mirror_show | 6 +- .../t04_mirror/CreateMirror3Test_mirror_show | 6 +- .../system/t04_mirror/CreateMirror4Test_gold | 2 +- .../t04_mirror/CreateMirror7Test_mirror_show | 6 +- .../t04_mirror/EditMirror6Test_mirror_show | 6 +- .../system/t04_mirror/ShowMirror1Test_gold | 6 +- .../system/t04_mirror/UpdateMirror11Test_gold | 2 +- .../system/t04_mirror/UpdateMirror1Test_gold | 14 +- .../smira/aptly/system/t04_mirror/create.py | 30 +- .../t05_snapshot/ListSnapshot7Test_gold | 1 + .../smira/aptly/system/t05_snapshot/list.py | 3 + .../t06_publish/PublishRepo12Test_release | 1 + .../t06_publish/PublishRepo15Test_release | 1 + .../t06_publish/PublishRepo17Test_release | 1 + .../t06_publish/PublishRepo1Test_release | 1 + .../t06_publish/PublishSnapshot13Test_release | 1 + .../t06_publish/PublishSnapshot15Test_release | 1 + .../t06_publish/PublishSnapshot16Test_release | 1 + .../t06_publish/PublishSnapshot17Test_release | 1 + .../PublishSnapshot1Test_packages_amd64 | 73 +- .../PublishSnapshot1Test_packages_i386 | 73 +- .../t06_publish/PublishSnapshot1Test_release | 1 + .../PublishSnapshot1Test_release_amd64 | 2 +- .../PublishSnapshot1Test_release_i386 | 2 +- .../t06_publish/PublishSnapshot24Test_release | 1 + .../t06_publish/PublishSnapshot26Test_release | 1 + .../t06_publish/PublishSnapshot2Test_release | 1 + .../PublishSnapshot35Test_packages_amd64 | 59 +- .../PublishSnapshot35Test_packages_i386 | 89 +- .../t06_publish/PublishSnapshot35Test_release | 1 + .../PublishSnapshot35Test_release_udeb_i386 | 2 +- .../t06_publish/PublishSnapshot3Test_release | 1 + .../t06_publish/PublishSnapshot4Test_release | 1 + .../t06_publish/PublishSwitch12Test_gold | 1 + .../t06_publish/PublishSwitch1Test_release | 1 + .../t06_publish/PublishSwitch2Test_binary | 203 +- .../t06_publish/PublishSwitch8Test_binaryA | 203 +- .../t06_publish/PublishSwitch8Test_release | 1 + .../t06_publish/PublishUpdate1Test_release | 1 + .../system/t06_publish/S3Publish1Test_release | 1 + .../system/t06_publish/S3Publish2Test_release | 1 + .../system/t06_publish/S3Publish3Test_release | 1 + .../t06_publish/SwiftPublish1Test_binary | 25 + .../system/t06_publish/SwiftPublish1Test_gold | 13 + .../t06_publish/SwiftPublish1Test_release | 35 + .../t06_publish/SwiftPublish1Test_sources | 42 + .../t06_publish/SwiftPublish2Test_binary | 25 + .../system/t06_publish/SwiftPublish2Test_gold | 8 + .../t06_publish/SwiftPublish2Test_release | 35 + .../t06_publish/SwiftPublish2Test_sources | 0 .../t06_publish/SwiftPublish3Test_binary | 29 + .../system/t06_publish/SwiftPublish3Test_gold | 8 + .../t06_publish/SwiftPublish3Test_release | 35 + .../system/t06_publish/SwiftPublish4Test_gold | 4 + .../system/t06_publish/SwiftPublish5Test_gold | 3 + .../aptly/system/t06_publish/__init__.py | 1 + .../smira/aptly/system/t06_publish/swift.py | 158 ++ .../smira/aptly/system/t06_publish/switch.py | 12 + .../pyspi_0.6.1-1.3.conflict.dsc | 12 + .../CleanupDB10Test/pyspi_0.6.1.orig.tar.gz | 0 .../aptly/system/t08_db/CleanupDB10Test_gold | 7 + .../aptly/system/t08_db/CleanupDB1Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB2Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB3Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB4Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB5Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB6Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB7Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB8Test_gold | 2 +- .../aptly/system/t08_db/CleanupDB9Test_gold | 7 + .../system/t08_db/CleanupDB9Test_publish_drop | 4 + .../smira/aptly/system/t08_db/cleanup.py | 32 + .../aptly/system/t09_repo/AddRepo14Test_gold | 2 + .../smira/aptly/system/t09_repo/add.py | 16 + .../aptly/system/t10_task/RunTask1Test_gold | 2 +- .../smira/aptly/system/t10_task/__init__.py | 3 +- .../t11_package/SearchPackage2Test_gold | 1 + .../smira/aptly/system/t11_package/search.py | 1 + .../smira/aptly/system/t12_api/__init__.py | 11 + .../smira/aptly/system/t12_api/files.py | 98 + .../smira/aptly/system/t12_api/graph.py | 17 + .../smira/aptly/system/t12_api/packages.py | 43 + .../smira/aptly/system/t12_api/publish.py | 278 +++ .../smira/aptly/system/t12_api/repos.py | 296 +++ .../smira/aptly/system/t12_api/snapshots.py | 276 +++ .../smira/aptly/system/t12_api/version.py | 10 + .../smira/aptly/utils/checksum_test.go | 3 +- .../smira/aptly/utils/compress_test.go | 3 +- src/github.com/smira/aptly/utils/config.go | 41 +- .../smira/aptly/utils/config_test.go | 19 +- src/github.com/smira/aptly/utils/gpg.go | 19 +- src/github.com/smira/aptly/utils/human.go | 8 +- .../smira/aptly/utils/human_test.go | 2 +- src/github.com/smira/aptly/utils/list_test.go | 2 +- .../smira/aptly/utils/utils_test.go | 3 +- 576 files changed, 34685 insertions(+), 10054 deletions(-) create mode 100644 bash_completion.d/aptly delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/codereview.cfg delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/keccakf.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/agent.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/channel.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server_terminal.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_linux.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/keys_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/tcpip_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/AlekSi/pointer/pointer.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_solaris.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_x.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.gitignore create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.travis.yml create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/AUTHORS.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/CHANGELOG.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/Godeps/Godeps.json create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/binding/binding.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/deprecated.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/app.yaml create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/hello.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/example_basic.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/example_pongo2.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/index.html create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/logger.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/mode.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/render/render.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/response_writer.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/routergroup.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/utils.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/.travis.yml create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/_example/main.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_others.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_windows.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.gitignore create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.travis.yml create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/COPYING create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/README.md create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/auth.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_0.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_1.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/doc.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/example_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/notes.txt create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_internal_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swifttest/server.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader_test.go delete mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/config.go rename src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/{error.go => errors.go} (50%) create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/ginkgo.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/.hgignore create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/AUTHORS create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/CONTRIBUTORS create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/LICENSE create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/README create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/lib/codereview/codereview.cfg create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/decode.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/encode.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy.go create mode 100644 src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto/.hgignore => golang.org/x/crypto/.gitignore} (87%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/AUTHORS (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/CONTRIBUTORS (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/LICENSE (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/PATENTS (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/README (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bcrypt/base64.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bcrypt/bcrypt.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bcrypt/bcrypt_test.go (96%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/blowfish/block.go (81%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/blowfish/blowfish_test.go (73%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/blowfish/cipher.go (89%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/blowfish/const.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/bn256.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/bn256_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/constants.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/curve.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/example_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/gfp12.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/gfp2.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/gfp6.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/optate.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/bn256/twist.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/cast5/cast5.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/cast5/cast5_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/const_amd64.s (81%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/cswap_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/curve25519.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/curve25519_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/doc.go (94%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/freeze_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/ladderstep_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/mont25519_amd64.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/mul_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/curve25519/square_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/hkdf/example_test.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/hkdf/hkdf.go (92%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/hkdf/hkdf_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/md4/md4.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/md4/md4_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/md4/md4block.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/nacl/box/box.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/nacl/box/box_test.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/nacl/secretbox/secretbox.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/nacl/secretbox/secretbox_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ocsp/ocsp.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ocsp/ocsp_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/armor/armor.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/armor/armor_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/armor/encode.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/canonical_text.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/canonical_text_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/clearsign/clearsign.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/clearsign/clearsign_test.go (83%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/elgamal/elgamal.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/elgamal/elgamal_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/errors/errors.go (87%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/keys.go (85%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/compressed.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/compressed_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/config.go (71%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/encrypted_key.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/encrypted_key_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/literal.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/ocfb.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/ocfb_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/one_pass_signature.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/opaque.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/opaque_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/packet.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/packet_test.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/private_key.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/private_key_test.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/public_key.go (94%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/public_key_test.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/public_key_v3.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/public_key_v3_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/reader.go (96%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/signature.go (94%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/signature_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/signature_v3.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/signature_v3_test.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/symmetric_key_encrypted.go (96%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/symmetric_key_encrypted_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/symmetrically_encrypted.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/symmetrically_encrypted_test.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/userattribute.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/userattribute_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/userid.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/packet/userid_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/read.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/read_test.go (89%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/s2k/s2k.go (61%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/s2k/s2k_test.go (81%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/write.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/openpgp/write_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/otr/libotr_test_helper.c (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/otr/otr.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/otr/otr_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/otr/smp.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/pbkdf2/pbkdf2.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/pbkdf2/pbkdf2_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/const_amd64.s (68%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/poly1305.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/poly1305_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/poly1305_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/sum_amd64.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/poly1305/sum_ref.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ripemd160/ripemd160.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ripemd160/ripemd160_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ripemd160/ripemd160block.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/hsalsa20.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/salsa2020_amd64.s (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/salsa208.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/salsa20_amd64.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/salsa20_ref.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa/salsa_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa20.go (95%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/salsa20/salsa20_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/scrypt/scrypt.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/scrypt/scrypt_test.go (100%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/doc.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/hashes.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakKats.json.deflate create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakf.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/register.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/shake.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/forward.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/keyring.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/testdata_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/benchmark_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/buffer.go (91%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/buffer_test.go (89%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/channel.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/client_test.go (76%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/common.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/connection.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/doc.go (87%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/example_test.go (68%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/kex.go (93%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/kex_test.go (93%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/keys.go (62%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/mac.go (80%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/mempipe_test.go (87%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/messages.go (67%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/messages_test.go (64%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/server.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/session.go (69%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/session_test.go (60%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/tcpip.go (65%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/tcpip_test.go (67%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/terminal/terminal.go (65%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/terminal/terminal_test.go (73%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/terminal/util.go (96%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/terminal/util_bsd.go (84%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_linux.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_windows.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/agent_unix_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/cert_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/test/doc.go (82%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/test/forward_unix_test.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/test/session_test.go (55%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/tcpip_test.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/test/test_unix_test.go (63%) create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/testdata_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/doc.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/keys.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata_test.go create mode 100644 src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport.go rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/ssh/transport_test.go (64%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/twofish/twofish.go (99%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/twofish/twofish_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/xtea/block.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/xtea/cipher.go (97%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/xtea/xtea_test.go (100%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/xts/xts.go (98%) rename src/github.com/smira/aptly/_vendor/src/{code.google.com/p/go.crypto => golang.org/x/crypto}/xts/xts_test.go (100%) create mode 100644 src/github.com/smira/aptly/api/api.go create mode 100644 src/github.com/smira/aptly/api/files.go create mode 100644 src/github.com/smira/aptly/api/graph.go create mode 100644 src/github.com/smira/aptly/api/packages.go create mode 100644 src/github.com/smira/aptly/api/publish.go create mode 100644 src/github.com/smira/aptly/api/repos.go create mode 100644 src/github.com/smira/aptly/api/router.go create mode 100644 src/github.com/smira/aptly/api/snapshot.go create mode 100644 src/github.com/smira/aptly/aptly/report.go create mode 100644 src/github.com/smira/aptly/cmd/api.go create mode 100644 src/github.com/smira/aptly/cmd/api_serve.go create mode 100644 src/github.com/smira/aptly/cmd/config.go create mode 100644 src/github.com/smira/aptly/cmd/config_show.go delete mode 100644 src/github.com/smira/aptly/console/terminal_bsd.go create mode 100644 src/github.com/smira/aptly/context/context.go create mode 100644 src/github.com/smira/aptly/deb/graph.go create mode 100644 src/github.com/smira/aptly/deb/import.go create mode 100644 src/github.com/smira/aptly/swift/public.go create mode 100644 src/github.com/smira/aptly/swift/public_test.go create mode 100644 src/github.com/smira/aptly/swift/swift.go create mode 100644 src/github.com/smira/aptly/swift/swift_test.go create mode 100644 src/github.com/smira/aptly/system/api_lib.py create mode 100644 src/github.com/smira/aptly/system/swift_lib.py create mode 100644 src/github.com/smira/aptly/system/t02_config/ConfigShowTest_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_mirror_show create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_gold create mode 100644 src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_mirror_show create mode 100644 src/github.com/smira/aptly/system/t06_publish/PublishSwitch12Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_sources create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_sources create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_binary create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_release create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish4Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/SwiftPublish5Test_gold create mode 100644 src/github.com/smira/aptly/system/t06_publish/swift.py create mode 100644 src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1-1.3.conflict.dsc create mode 100644 src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1.orig.tar.gz create mode 100644 src/github.com/smira/aptly/system/t08_db/CleanupDB10Test_gold create mode 100644 src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_gold create mode 100644 src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_publish_drop create mode 100644 src/github.com/smira/aptly/system/t09_repo/AddRepo14Test_gold create mode 100644 src/github.com/smira/aptly/system/t12_api/__init__.py create mode 100644 src/github.com/smira/aptly/system/t12_api/files.py create mode 100644 src/github.com/smira/aptly/system/t12_api/graph.py create mode 100644 src/github.com/smira/aptly/system/t12_api/packages.py create mode 100644 src/github.com/smira/aptly/system/t12_api/publish.py create mode 100644 src/github.com/smira/aptly/system/t12_api/repos.py create mode 100644 src/github.com/smira/aptly/system/t12_api/snapshots.py create mode 100644 src/github.com/smira/aptly/system/t12_api/version.py diff --git a/bash_completion.d/aptly b/bash_completion.d/aptly new file mode 100644 index 00000000..9cfd752f --- /dev/null +++ b/bash_completion.d/aptly @@ -0,0 +1,576 @@ +#!/bin/bash + +# (The MIT License) +# +# Copyright (c) 2014 Andrey Smirnov +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +__aptly_mirror_list() +{ + aptly mirror list -raw +} + +__aptly_repo_list() +{ + aptly repo list -raw +} + +__aptly_snapshot_list() +{ + aptly snapshot list -raw +} + +__aptly_published_distributions() +{ + aptly publish list -raw | cut -d ' ' -f 2 | sort | uniq +} + +__aptly_published_prefixes() +{ + aptly publish list -raw | cut -d ' ' -f 1 | sort | uniq +} + +__aptly_prefixes_for_distribution() +{ + aptly publish list -raw | awk -v dist="$1" '{ if (dist == $2) print $1 }' | sort | uniq +} + +_aptly() +{ + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + commands="api config db graph mirror package publish repo serve snapshot task version" + options="-architectures= -config= -dep-follow-all-variants -dep-follow-recommends -dep-follow-source -dep-follow-suggests" + + db_subcommands="cleanup recover" + mirror_subcommands="create drop show list rename search update" + publish_subcommands="drop list repo snapshot switch update" + snapshot_subcommands="create diff drop filter list merge pull rename search show verify" + repo_subcommands="add copy create drop edit import list move remove rename search show" + package_subcommands="search show" + task_subcommands="run" + config_subcommands="show" + api_subcommands="serve" + + local cmd subcmd numargs numoptions i + + numargs=0 + numoptions=0 + + for (( i=1; i < $COMP_CWORD; i++ )); do + if [[ -n "$cmd" ]]; then + if [[ ! -n "$subcmd" ]]; then + subcmd=${COMP_WORDS[i]} + numargs=$(( COMP_CWORD - i - 1 )) + else + if [[ "${COMP_WORDS[i]}" == -* ]]; then + numoptions=$(( numoptions + 1 )) + numargs=$(( numargs - 1 )) + fi + fi + else + if [[ ! "${COMP_WORDS[i]}" == -* ]]; then + cmd=${COMP_WORDS[i]} + fi + fi + done + + if [[ ! -n "$cmd" ]]; + then + case "$cur" in + -*) + COMPREPLY=($(compgen -W "${options}" -- ${cur})) + return 0 + ;; + *) + COMPREPLY=($(compgen -W "${commands}" -- ${cur})) + return 0 + ;; + esac + fi + + if [[ ! -n "$subcmd" ]]; + then + case "$prev" in + "db") + COMPREPLY=($(compgen -W "${db_subcommands}" -- ${cur})) + return 0 + ;; + "mirror") + COMPREPLY=($(compgen -W "${mirror_subcommands}" -- ${cur})) + return 0 + ;; + "repo") + COMPREPLY=($(compgen -W "${repo_subcommands}" -- ${cur})) + return 0 + ;; + "snapshot") + COMPREPLY=($(compgen -W "${snapshot_subcommands}" -- ${cur})) + return 0 + ;; + "publish") + COMPREPLY=($(compgen -W "${publish_subcommands}" -- ${cur})) + return 0 + ;; + "package") + COMPREPLY=($(compgen -W "${package_subcommands}" -- ${cur})) + return 0 + ;; + "task") + COMPREPLY=($(compgen -W "${task_subcommands}" -- ${cur})) + return 0 + ;; + "config") + COMPREPLY=($(compgen -W "${config_subcommands}" -- ${cur})) + return 0 + ;; + "api") + COMPREPLY=($(compgen -W "${api_subcommands}" -- ${cur})) + return 0 + ;; + *) + ;; + esac + fi + + case "$cmd" in + "mirror") + case "$subcmd" in + "create") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-sources -with-udebs" -- ${cur})) + return 0 + fi + fi + ;; + "edit") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-filter= -filter-with-deps -with-sources -with-udebs" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + fi + ;; + "show") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-packages" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + fi + ;; + "search") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + fi + ;; + "rename") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + return 0 + fi + ;; + "drop") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-force" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + fi + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw" -- ${cur})) + return 0 + fi + ;; + "update") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-force -download-limit= -ignore-checksums -ignore-signatures -keyring=" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + fi + ;; + esac + ;; + "repo") + case "$subcmd" in + "add") + case $numargs in + 0) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-force-replace -remove-files" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + ;; + 1) + local files=$(find . -mindepth 1 -maxdepth 1 \( -type d -or -name \*.deb -or -name \*.dsc \) -exec basename {} \;) + COMPREPLY=($(compgen -W "${files}" -- ${cur})) + return 0 + ;; + esac + ;; + "copy"|"move") + case $numargs in + 0) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps -dry-run" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + ;; + 1) + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + return 0 + ;; + esac + ;; + "create") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-comment= -distribution= -component=" -- ${cur})) + return 0 + fi + fi + ;; + "drop") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-force" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + fi + ;; + "edit") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-comment= -distribution= -component=" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + fi + ;; + "search") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + fi + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw" -- ${cur})) + return 0 + fi + ;; + "import") + case $numargs in + 0) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps -dry-run" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + fi + return 0 + ;; + 1) + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + return 0 + ;; + esac + ;; + "remove") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-dry-run" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + fi + ;; + "show") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-packages" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + return 0 + fi + ;; + "rename") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + return 0 + fi + ;; + esac + ;; + "snapshot") + case "$subcmd" in + "create") + case $numargs in + 1) + COMPREPLY=($(compgen -W "from empty" -- ${cur})) + return 0 + ;; + 2) + if [[ "$prev" == "from" ]]; then + COMPREPLY=($(compgen -W "mirror repo" -- ${cur})) + return 0 + fi + ;; + 3) + if [[ "$prev" == "mirror" ]]; then + COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur})) + return 0 + fi + if [[ "$prev" == "repo" ]]; then + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + return 0 + fi + ;; + esac + ;; + "diff") + if [[ $numargs -eq 0 ]] && [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-only-matching" -- ${cur})) + return 0 + fi + + if [[ $numargs -lt 2 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + return 0 + fi + ;; + "drop") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-force" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + fi + return 0 + fi + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw -sort=" -- ${cur})) + return 0 + fi + ;; + "merge") + if [[ $numargs -gt 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-latest" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + fi + return 0 + fi + ;; + "pull") + if [[ $numargs -eq 0 ]] && [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-all-matches -dry-run -no-deps -no-remove" -- ${cur})) + return 0 + fi + + if [[ $numargs -lt 2 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + return 0 + fi + ;; + "filter") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + fi + return 0 + fi + ;; + "show") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-packages" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + fi + return 0 + fi + ;; + "search") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-deps" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + fi + return 0 + fi + ;; + "rename") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + return 0 + fi + ;; + "verify") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + return 0 + fi + ;; + esac + ;; + "publish") + case "$subcmd" in + "snapshot"|"repo") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-batch -force-overwrite -distribution= -component= -gpg-key= -keyring= -label= -origin= -passphrase= -passphrase-file= -secret-keyring= -skip-signing" -- ${cur})) + else + if [[ "$subcmd" == "snapshot" ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_repo_list)" -- ${cur})) + fi + fi + return 0 + fi + + if [[ $numargs -eq 1 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_published_prefixes)" -- ${cur})) + return 0 + fi + ;; + "list") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "-raw" -- ${cur})) + return 0 + fi + ;; + "update") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-batch -force-overwrite -gpg-key= -keyring= -passphrase= -passphrase-file= -secret-keyring= -skip-signing" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_published_distributions)" -- ${cur})) + fi + return 0 + fi + + if [[ $numargs -eq 1 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_prefixes_for_distribution $prev)" -- ${cur})) + return 0 + fi + ;; + "switch") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-batch -force-overwrite -component= -gpg-key= -keyring= -passphrase= -passphrase-file= -secret-keyring= -skip-signing" -- ${cur})) + else + COMPREPLY=($(compgen -W "$(__aptly_published_distributions)" -- ${cur})) + fi + return 0 + fi + + if [[ $numargs -eq 1 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_prefixes_for_distribution $prev)" -- ${cur})) + return 0 + fi + + if [[ $numargs -ge 2 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_snapshot_list)" -- ${cur})) + return 0 + fi + ;; + "drop") + if [[ $numargs -eq 0 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_published_distributions)" -- ${cur})) + return 0 + fi + + if [[ $numargs -eq 1 ]]; then + COMPREPLY=($(compgen -W "$(__aptly_prefixes_for_distribution $prev)" -- ${cur})) + return 0 + fi + ;; + esac + ;; + "package") + case "$subcmd" in + "show") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-with-files -with-references" -- ${cur})) + fi + return 0 + fi + ;; + esac + ;; + "serve") + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-listen=" -- ${cur})) + return 0 + fi + ;; + "api") + case "$subcmd" in + "serve") + if [[ $numargs -eq 0 ]]; then + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-listen=" -- ${cur})) + fi + return 0 + fi + ;; + esac + ;; + esac +} && complete -F _aptly aptly diff --git a/src/github.com/smira/aptly/.travis.yml b/src/github.com/smira/aptly/.travis.yml index 26b65e68..30d2f40c 100644 --- a/src/github.com/smira/aptly/.travis.yml +++ b/src/github.com/smira/aptly/.travis.yml @@ -1,17 +1,20 @@ language: go go: - - 1.2.1 - - 1.3.1 + - 1.3.3 - tip env: global: - secure: "YSwtFrMqh4oUvdSQTXBXMHHLWeQgyNEL23ChIZwU0nuDGIcQZ65kipu0PzefedtUbK4ieC065YCUi4UDDh6gPotB/Wu1pnYg3dyQ7rFvhaVYAAUEpajAdXZhlx+7+J8a4FZMeC/kqiahxoRgLbthF9019ouIqhGB9zHKI6/yZwc=" - secure: "V7OjWrfQ8UbktgT036jYQPb/7GJT3Ol9LObDr8FYlzsQ+F1uj2wLac6ePuxcOS4FwWOJinWGM1h+JiFkbxbyFqfRNJ0jj0O2p93QyDojxFVOn1mXqqvV66KFqAWR2Vzkny/gDvj8LTvdB1cgAIm2FNOkQc6E1BFnyWS2sN9ea5E=" + - secure: "OxiVNmre2JzUszwPNNilKDgIqtfX2gnRSsVz6nuySB1uO2yQsOQmKWJ9cVYgH2IB5H8eWXKOhexcSE28kz6TPLRuEcU9fnqKY3uEkdwm7rJfz9lf+7C4bJEUdA1OIzJppjnWUiXxD7CEPL1DlnMZM24eDQYqa/4WKACAgkK53gE=" before_install: - sudo apt-get update -qq - - sudo apt-get install -y python-boto + - sudo apt-get install -y python-virtualenv graphviz + - virtualenv env + - . env/bin/activate + - pip install boto requests python-swiftclient install: - make prepare @@ -21,3 +24,12 @@ script: make travis matrix: allow_failures: - go: tip + + +notifications: + webhooks: + urls: + - "https://webhooks.gitter.im/e/c691da114a41eed6ec45" + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: false # default: false diff --git a/src/github.com/smira/aptly/AUTHORS b/src/github.com/smira/aptly/AUTHORS index 17fdfe01..2769f327 100644 --- a/src/github.com/smira/aptly/AUTHORS +++ b/src/github.com/smira/aptly/AUTHORS @@ -3,5 +3,15 @@ List of contributors, in chronological order: * Andrey Smirnov (https://github.com/smira) * Sebastien Binet (https://github.com/sbinet) * Ryan Uber (https://github.com/ryanuber) -* Simon Aquino (https://github.com/simonaquino) -* Vincent Batoufflet (https://github.com/vbatoufflet) \ No newline at end of file +* Simon Aquino (https://github.com/queeno) +* Vincent Batoufflet (https://github.com/vbatoufflet) +* Ivan Kurnosov (https://github.com/zerkms) +* Dmitrii Kashin (https://github.com/freehck) +* Chris Read (https://github.com/cread) +* Rohan Garg (https://github.com/shadeslayer) +* Russ Allbery (https://github.com/rra) +* Sylvain Baubeau (https://github.com/lebauce) +* Andrea Bernardo Ciddio (https://github.com/bcandrea) +* Michael Koval (https://github.com/mkoval) +* Alexander Guy (https://github.com/alexanderguy) +* Sebastien Badia (https://github.com/sbadia) diff --git a/src/github.com/smira/aptly/Gomfile b/src/github.com/smira/aptly/Gomfile index 910bce2b..7ef20532 100644 --- a/src/github.com/smira/aptly/Gomfile +++ b/src/github.com/smira/aptly/Gomfile @@ -1,27 +1,33 @@ gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5' -gom 'code.google.com/p/go.crypto/ssh/terminal', :commit => '7aa593ce8cea' gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2' gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556' gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793' -gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827' -gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1' +gom 'github.com/AlekSi/pointer', :commit => '5f6d527dae3d678b46fbb20331ddf44e2b841943' +gom 'github.com/cheggaaa/pb', :commit => '2c1b74620cc58a81ac152ee2d322e28c806d81ed' +gom 'github.com/gin-gonic/gin', :commit => 'b1758d3bfa09e61ddbc1c9a627e936eec6a170de' +gom 'github.com/jlaffaye/ftp', :commit => 'fec71e62e457557fbe85cefc847a048d57815d76' +gom 'github.com/julienschmidt/httprouter', :commit => '46807412fe50aaceb73bb57061c2230fd26a1640' gom 'github.com/mattn/go-shellwords', :commit => 'c7ca6f94add751566a61cf2199e1de78d4c3eee4' gom 'github.com/mitchellh/goamz/s3', :commit => 'e7664b32019f31fd1bdf33f9e85f28722f700405' gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b' +gom 'github.com/ncw/swift', :commit => '384ef27c70645e285f8bb9d02276bf654d06027e' gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47' gom 'github.com/smira/flag', :commit => '357ed3e599ffcbd4aeaa828e1d10da2df3ea5107' gom 'github.com/smira/go-ftp-protocol/protocol', :commit => '066b75c2b70dca7ae10b1b88b47534a3c31ccfaa' -gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'e2fa4e6ac1cc41a73bc9fd467878ecbf65df5cc3' +gom 'github.com/syndtr/gosnappy/snappy', :commit => 'ce8acff4829e0c2458a67ead32390ac0a381c862' +gom 'github.com/syndtr/goleveldb/leveldb', :commit => '97e257099d2ab9578151ba85e2641e2cd14d3ca8' gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b' +gom 'github.com/vaughan0/go-ini', :commit => 'a98ad7ee00ec53921f08832bc06ecf7fd600e6a1' gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08' +gom 'golang.org/x/crypto/ssh/terminal', :commit => 'a7ead6ddf06233883deca151dffaef2effbf498f' group :test do - gom 'launchpad.net/gocheck' + gom 'gopkg.in/check.v1' end group :development do gom 'github.com/golang/lint/golint' gom 'github.com/mattn/goveralls' gom 'github.com/axw/gocov/gocov' - gom 'code.google.com/p/go.tools/cmd/cover' + gom 'golang.org/x/tools/cmd/cover' end diff --git a/src/github.com/smira/aptly/Makefile b/src/github.com/smira/aptly/Makefile index 1eccbc3c..a4c224aa 100644 --- a/src/github.com/smira/aptly/Makefile +++ b/src/github.com/smira/aptly/Makefile @@ -1,6 +1,6 @@ GOVERSION=$(shell go version | awk '{print $$3;}') -PACKAGES=database deb files http query s3 utils -ALL_PACKAGES=aptly cmd console database deb files http query s3 utils +PACKAGES=context database deb files http query swift s3 utils +ALL_PACKAGES=api aptly context cmd console database deb files http query swift s3 utils BINPATH=$(abspath ./_vendor/bin) GOM_ENVIRONMENT=-test PYTHON?=python @@ -36,7 +36,7 @@ coverage: coverage.out rm -f coverage.out check: - $(GOM) exec go tool vet -all=true -shadow=true $(ALL_PACKAGES:%=./%) + $(GOM) exec go tool vet -all=true $(ALL_PACKAGES:%=./%) $(GOM) exec golint $(ALL_PACKAGES:%=./%) install: @@ -67,7 +67,7 @@ package: (cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly) gzip root/usr/share/man/man1/aptly.1 fpm -s dir -t deb -n aptly -v $(VERSION) --url=http://www.aptly.info/ --license=MIT --vendor="Andrey Smirnov " \ - -f -m "Andrey Smirnov " --description="Debian repository management tool" --deb-recommends bzip2 -C root/ . + -f -m "Andrey Smirnov " --description="Debian repository management tool" --deb-recommends bzip2 --deb-recommends graphviz -C root/ . mv aptly_$(VERSION)_*.deb ~ src-package: @@ -77,6 +77,8 @@ src-package: cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin} + mkdir -p aptly-$(VERSION)/bash_completion.d + (cd aptly-$(VERSION)/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/$(VERSION)/aptly) tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION) rm -rf aptly-$(VERSION) curl -T aptly-$(VERSION)-src.tar.bz2 -usmira:$(BINTRAY_KEY) https://api.bintray.com/content/smira/aptly/aptly/$(VERSION)/$(VERSION)/aptly-$(VERSION)-src.tar.bz2 diff --git a/src/github.com/smira/aptly/README.rst b/src/github.com/smira/aptly/README.rst index be90bb6c..14c17046 100644 --- a/src/github.com/smira/aptly/README.rst +++ b/src/github.com/smira/aptly/README.rst @@ -8,8 +8,11 @@ aptly .. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD :target: https://coveralls.io/r/smira/aptly?branch=HEAD -.. image:: http://gobuild.io/badge/github.com/smira/aptly/download.png - :target: http://gobuild.io/github.com/smira/aptly +.. image:: https://badges.gitter.im/Join Chat.svg + :target: https://gitter.im/smira/aptly?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +.. image:: http://goreportcard.com/badge/gojp/goreportcard + :target: http://goreportcard.com/report/gojp/goreportcard Aptly is a swiss army knife for Debian repository management. @@ -28,6 +31,7 @@ Aptly features: ("+" means planned features) * merge two or more snapshots into one * filter repository by search query, pulling dependencies when required * publish self-made packages as Debian repositories +* REST API for remote access * mirror repositories "as-is" (without resigning with user's key) (+) * support for yum repositories (+) @@ -44,8 +48,7 @@ To install aptly on Debian/Ubuntu, add new repository to /etc/apt/sources.list:: And import key that is used to sign the release:: - $ gpg --keyserver keys.gnupg.net --recv-keys 2A194991 - $ gpg -a --export 2A194991 | sudo apt-key add - + $ apt-key adv --keyserver keys.gnupg.net --recv-keys E083A3782A194991 After that you can install aptly as any other software package:: @@ -55,9 +58,13 @@ After that you can install aptly as any other software package:: Don't worry about squeeze part in repo name: aptly package should work on Debian squeeze+, Ubuntu 10.0+. Package contains aptly binary, man page and bash completion. +If you would like to use nightly builds (unstable), please use following repository:: + + deb http://repo.aptly.info/ nightly main + Binary executables (depends almost only on libc) are available for download from `Bintray `_. -If you have Go environment set up, you can build aptly from source by running (go 1.2+ required):: +If you have Go environment set up, you can build aptly from source by running (go 1.3+ required):: go get -u github.com/mattn/gom mkdir -p $GOPATH/src/github.com/smira/aptly diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/codereview.cfg b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/codereview.cfg deleted file mode 100644 index 43dbf3ce..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/codereview.cfg +++ /dev/null @@ -1,2 +0,0 @@ -defaultcc: golang-codereviews@googlegroups.com -contributors: http://go.googlecode.com/hg/CONTRIBUTORS diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys_test.go deleted file mode 100644 index d0d9621e..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package openpgp - -import ( - "testing" - "time" -) - -func TestKeyExpiry(t *testing.T) { - kring, _ := ReadKeyRing(readerFromHex(expiringKeyHex)) - entity := kring[0] - - const timeFormat = "2006-01-02" - time1, _ := time.Parse(timeFormat, "2013-07-01") - // The expiringKeyHex key is structured as: - // - // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC - // sub 1024R/1ABB25A0 created: 2013-07-01 expires: 2013-07-08 usage: E - // sub 1024R/96A672F5 created: 2013-07-01 expires: 2013-07-31 usage: E - // - // So this should select the first, non-expired encryption key. - key, _ := entity.encryptionKey(time1) - if id := key.PublicKey.KeyIdShortString(); id != "1ABB25A0" { - t.Errorf("Expected key 1ABB25A0 at time %s, but got key %s", time1.Format(timeFormat), id) - } - - // Once the first encryption subkey has expired, the second should be - // selected. - time2, _ := time.Parse(timeFormat, "2013-07-09") - key, _ = entity.encryptionKey(time2) - if id := key.PublicKey.KeyIdShortString(); id != "96A672F5" { - t.Errorf("Expected key 96A672F5 at time %s, but got key %s", time2.Format(timeFormat), id) - } - - // Once all the keys have expired, nothing should be returned. - time3, _ := time.Parse(timeFormat, "2013-08-01") - if key, ok := entity.encryptionKey(time3); ok { - t.Errorf("Expected no key at time %s, but got key %s", time3.Format(timeFormat), key.PublicKey.KeyIdShortString()) - } -} - -const expiringKeyHex = "988d0451d1ec5d010400ba3385721f2dc3f4ab096b2ee867ab77213f0a27a8538441c35d2fa225b08798a1439a66a5150e6bdc3f40f5d28d588c712394c632b6299f77db8c0d48d37903fb72ebd794d61be6aa774688839e5fdecfe06b2684cc115d240c98c66cb1ef22ae84e3aa0c2b0c28665c1e7d4d044e7f270706193f5223c8d44e0d70b7b8da830011010001b40f4578706972792074657374206b657988be041301020028050251d1ec5d021b03050900278d00060b090807030206150802090a0b0416020301021e01021780000a091072589ad75e237d8c033503fd10506d72837834eb7f994117740723adc39227104b0d326a1161871c0b415d25b4aedef946ca77ea4c05af9c22b32cf98be86ab890111fced1ee3f75e87b7cc3c00dc63bbc85dfab91c0dc2ad9de2c4d13a34659333a85c6acc1a669c5e1d6cecb0cf1e56c10e72d855ae177ddc9e766f9b2dda57ccbb75f57156438bbdb4e42b88d0451d1ec5d0104009c64906559866c5cb61578f5846a94fcee142a489c9b41e67b12bb54cfe86eb9bc8566460f9a720cb00d6526fbccfd4f552071a8e3f7744b1882d01036d811ee5a3fb91a1c568055758f43ba5d2c6a9676b012f3a1a89e47bbf624f1ad571b208f3cc6224eb378f1645dd3d47584463f9eadeacfd1ce6f813064fbfdcc4b5a53001101000188a504180102000f021b0c050251d1f06b050900093e89000a091072589ad75e237d8c20e00400ab8310a41461425b37889c4da28129b5fae6084fafbc0a47dd1adc74a264c6e9c9cc125f40462ee1433072a58384daef88c961c390ed06426a81b464a53194c4e291ddd7e2e2ba3efced01537d713bd111f48437bde2363446200995e8e0d4e528dda377fd1e8f8ede9c8e2198b393bd86852ce7457a7e3daf74d510461a5b77b88d0451d1ece8010400b3a519f83ab0010307e83bca895170acce8964a044190a2b368892f7a244758d9fc193482648acb1fb9780d28cc22d171931f38bb40279389fc9bf2110876d4f3db4fcfb13f22f7083877fe56592b3b65251312c36f83ffcb6d313c6a17f197dd471f0712aad15a8537b435a92471ba2e5b0c72a6c72536c3b567c558d7b6051001101000188a504180102000f021b0c050251d1f07b050900279091000a091072589ad75e237d8ce69e03fe286026afacf7c97ee20673864d4459a2240b5655219950643c7dba0ac384b1d4359c67805b21d98211f7b09c2a0ccf6410c8c04d4ff4a51293725d8d6570d9d8bb0e10c07d22357caeb49626df99c180be02d77d1fe8ed25e7a54481237646083a9f89a11566cd20b9e995b1487c5f9e02aeb434f3a1897cd416dd0a87861838da3e9e" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/keccakf.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/keccakf.go deleted file mode 100644 index 76c0312a..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/keccakf.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// This file implements the core Keccak permutation function necessary for computing SHA3. -// This is implemented in a separate file to allow for replacement by an optimized implementation. -// Nothing in this package is exported. -// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/). - -// rc stores the round constants for use in the ι step. -var rc = [...]uint64{ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808A, - 0x8000000080008000, - 0x000000000000808B, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008A, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000A, - 0x000000008000808B, - 0x800000000000008B, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800A, - 0x800000008000000A, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -} - -// keccakF computes the complete Keccak-f function consisting of 24 rounds with a different -// constant (rc) in each round. This implementation fully unrolls the round function to avoid -// inner loops, as well as pre-calculating shift offsets. -func keccakF(a *[numLanes]uint64) { - var t, bc0, bc1, bc2, bc3, bc4 uint64 - for _, roundConstant := range rc { - // θ step - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - t = bc4 ^ (bc1<<1 ^ bc1>>63) - a[0] ^= t - a[5] ^= t - a[10] ^= t - a[15] ^= t - a[20] ^= t - t = bc0 ^ (bc2<<1 ^ bc2>>63) - a[1] ^= t - a[6] ^= t - a[11] ^= t - a[16] ^= t - a[21] ^= t - t = bc1 ^ (bc3<<1 ^ bc3>>63) - a[2] ^= t - a[7] ^= t - a[12] ^= t - a[17] ^= t - a[22] ^= t - t = bc2 ^ (bc4<<1 ^ bc4>>63) - a[3] ^= t - a[8] ^= t - a[13] ^= t - a[18] ^= t - a[23] ^= t - t = bc3 ^ (bc0<<1 ^ bc0>>63) - a[4] ^= t - a[9] ^= t - a[14] ^= t - a[19] ^= t - a[24] ^= t - - // ρ and π steps - t = a[1] - t, a[10] = a[10], t<<1^t>>(64-1) - t, a[7] = a[7], t<<3^t>>(64-3) - t, a[11] = a[11], t<<6^t>>(64-6) - t, a[17] = a[17], t<<10^t>>(64-10) - t, a[18] = a[18], t<<15^t>>(64-15) - t, a[3] = a[3], t<<21^t>>(64-21) - t, a[5] = a[5], t<<28^t>>(64-28) - t, a[16] = a[16], t<<36^t>>(64-36) - t, a[8] = a[8], t<<45^t>>(64-45) - t, a[21] = a[21], t<<55^t>>(64-55) - t, a[24] = a[24], t<<2^t>>(64-2) - t, a[4] = a[4], t<<14^t>>(64-14) - t, a[15] = a[15], t<<27^t>>(64-27) - t, a[23] = a[23], t<<41^t>>(64-41) - t, a[19] = a[19], t<<56^t>>(64-56) - t, a[13] = a[13], t<<8^t>>(64-8) - t, a[12] = a[12], t<<25^t>>(64-25) - t, a[2] = a[2], t<<43^t>>(64-43) - t, a[20] = a[20], t<<62^t>>(64-62) - t, a[14] = a[14], t<<18^t>>(64-18) - t, a[22] = a[22], t<<39^t>>(64-39) - t, a[9] = a[9], t<<61^t>>(64-61) - t, a[6] = a[6], t<<20^t>>(64-20) - a[1] = t<<44 ^ t>>(64-44) - - // χ step - bc0 = a[0] - bc1 = a[1] - bc2 = a[2] - bc3 = a[3] - bc4 = a[4] - a[0] ^= bc2 &^ bc1 - a[1] ^= bc3 &^ bc2 - a[2] ^= bc4 &^ bc3 - a[3] ^= bc0 &^ bc4 - a[4] ^= bc1 &^ bc0 - bc0 = a[5] - bc1 = a[6] - bc2 = a[7] - bc3 = a[8] - bc4 = a[9] - a[5] ^= bc2 &^ bc1 - a[6] ^= bc3 &^ bc2 - a[7] ^= bc4 &^ bc3 - a[8] ^= bc0 &^ bc4 - a[9] ^= bc1 &^ bc0 - bc0 = a[10] - bc1 = a[11] - bc2 = a[12] - bc3 = a[13] - bc4 = a[14] - a[10] ^= bc2 &^ bc1 - a[11] ^= bc3 &^ bc2 - a[12] ^= bc4 &^ bc3 - a[13] ^= bc0 &^ bc4 - a[14] ^= bc1 &^ bc0 - bc0 = a[15] - bc1 = a[16] - bc2 = a[17] - bc3 = a[18] - bc4 = a[19] - a[15] ^= bc2 &^ bc1 - a[16] ^= bc3 &^ bc2 - a[17] ^= bc4 &^ bc3 - a[18] ^= bc0 &^ bc4 - a[19] ^= bc1 &^ bc0 - bc0 = a[20] - bc1 = a[21] - bc2 = a[22] - bc3 = a[23] - bc4 = a[24] - a[20] ^= bc2 &^ bc1 - a[21] ^= bc3 &^ bc2 - a[22] ^= bc4 &^ bc3 - a[23] ^= bc0 &^ bc4 - a[24] ^= bc1 &^ bc0 - - // ι step - a[0] ^= roundConstant - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3.go deleted file mode 100644 index e1f9aa85..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package sha3 implements the SHA3 hash algorithm (formerly called Keccak) chosen by NIST in 2012. -// This file provides a SHA3 implementation which implements the standard hash.Hash interface. -// Writing input data, including padding, and reading output data are computed in this file. -// Note that the current implementation can compute the hash of an integral number of bytes only. -// This is a consequence of the hash interface in which a buffer of bytes is passed in. -// The internals of the Keccak-f function are computed in keccakf.go. -// For the detailed specification, refer to the Keccak web site (http://keccak.noekeon.org/). -package sha3 - -import ( - "encoding/binary" - "hash" -) - -// laneSize is the size in bytes of each "lane" of the internal state of SHA3 (5 * 5 * 8). -// Note that changing this size would requires using a type other than uint64 to store each lane. -const laneSize = 8 - -// sliceSize represents the dimensions of the internal state, a square matrix of -// sliceSize ** 2 lanes. This is the size of both the "rows" and "columns" dimensions in the -// terminology of the SHA3 specification. -const sliceSize = 5 - -// numLanes represents the total number of lanes in the state. -const numLanes = sliceSize * sliceSize - -// stateSize is the size in bytes of the internal state of SHA3 (5 * 5 * WSize). -const stateSize = laneSize * numLanes - -// digest represents the partial evaluation of a checksum. -// Note that capacity, and not outputSize, is the critical security parameter, as SHA3 can output -// an arbitrary number of bytes for any given capacity. The Keccak proposal recommends that -// capacity = 2*outputSize to ensure that finding a collision of size outputSize requires -// O(2^{outputSize/2}) computations (the birthday lower bound). Future standards may modify the -// capacity/outputSize ratio to allow for more output with lower cryptographic security. -type digest struct { - a [numLanes]uint64 // main state of the hash - outputSize int // desired output size in bytes - capacity int // number of bytes to leave untouched during squeeze/absorb - absorbed int // number of bytes absorbed thus far -} - -// minInt returns the lesser of two integer arguments, to simplify the absorption routine. -func minInt(v1, v2 int) int { - if v1 <= v2 { - return v1 - } - return v2 -} - -// rate returns the number of bytes of the internal state which can be absorbed or squeezed -// in between calls to the permutation function. -func (d *digest) rate() int { - return stateSize - d.capacity -} - -// Reset clears the internal state by zeroing bytes in the state buffer. -// This can be skipped for a newly-created hash state; the default zero-allocated state is correct. -func (d *digest) Reset() { - d.absorbed = 0 - for i := range d.a { - d.a[i] = 0 - } -} - -// BlockSize, required by the hash.Hash interface, does not have a standard intepretation -// for a sponge-based construction like SHA3. We return the data rate: the number of bytes which -// can be absorbed per invocation of the permutation function. For Merkle-Damgård based hashes -// (ie SHA1, SHA2, MD5) the output size of the internal compression function is returned. -// We consider this to be roughly equivalent because it represents the number of bytes of output -// produced per cryptographic operation. -func (d *digest) BlockSize() int { return d.rate() } - -// Size returns the output size of the hash function in bytes. -func (d *digest) Size() int { - return d.outputSize -} - -// unalignedAbsorb is a helper function for Write, which absorbs data that isn't aligned with an -// 8-byte lane. This requires shifting the individual bytes into position in a uint64. -func (d *digest) unalignedAbsorb(p []byte) { - var t uint64 - for i := len(p) - 1; i >= 0; i-- { - t <<= 8 - t |= uint64(p[i]) - } - offset := (d.absorbed) % d.rate() - t <<= 8 * uint(offset%laneSize) - d.a[offset/laneSize] ^= t - d.absorbed += len(p) -} - -// Write "absorbs" bytes into the state of the SHA3 hash, updating as needed when the sponge -// "fills up" with rate() bytes. Since lanes are stored internally as type uint64, this requires -// converting the incoming bytes into uint64s using a little endian interpretation. This -// implementation is optimized for large, aligned writes of multiples of 8 bytes (laneSize). -// Non-aligned or uneven numbers of bytes require shifting and are slower. -func (d *digest) Write(p []byte) (int, error) { - // An initial offset is needed if the we aren't absorbing to the first lane initially. - offset := d.absorbed % d.rate() - toWrite := len(p) - - // The first lane may need to absorb unaligned and/or incomplete data. - if (offset%laneSize != 0 || len(p) < 8) && len(p) > 0 { - toAbsorb := minInt(laneSize-(offset%laneSize), len(p)) - d.unalignedAbsorb(p[:toAbsorb]) - p = p[toAbsorb:] - offset = (d.absorbed) % d.rate() - - // For every rate() bytes absorbed, the state must be permuted via the F Function. - if (d.absorbed)%d.rate() == 0 { - keccakF(&d.a) - } - } - - // This loop should absorb the bulk of the data into full, aligned lanes. - // It will call the update function as necessary. - for len(p) > 7 { - firstLane := offset / laneSize - lastLane := minInt(d.rate()/laneSize, firstLane+len(p)/laneSize) - - // This inner loop absorbs input bytes into the state in groups of 8, converted to uint64s. - for lane := firstLane; lane < lastLane; lane++ { - d.a[lane] ^= binary.LittleEndian.Uint64(p[:laneSize]) - p = p[laneSize:] - } - d.absorbed += (lastLane - firstLane) * laneSize - // For every rate() bytes absorbed, the state must be permuted via the F Function. - if (d.absorbed)%d.rate() == 0 { - keccakF(&d.a) - } - - offset = 0 - } - - // If there are insufficient bytes to fill the final lane, an unaligned absorption. - // This should always start at a correct lane boundary though, or else it would be caught - // by the uneven opening lane case above. - if len(p) > 0 { - d.unalignedAbsorb(p) - } - - return toWrite, nil -} - -// pad computes the SHA3 padding scheme based on the number of bytes absorbed. -// The padding is a 1 bit, followed by an arbitrary number of 0s and then a final 1 bit, such that -// the input bits plus padding bits are a multiple of rate(). Adding the padding simply requires -// xoring an opening and closing bit into the appropriate lanes. -func (d *digest) pad() { - offset := d.absorbed % d.rate() - // The opening pad bit must be shifted into position based on the number of bytes absorbed - padOpenLane := offset / laneSize - d.a[padOpenLane] ^= 0x0000000000000001 << uint(8*(offset%laneSize)) - // The closing padding bit is always in the last position - padCloseLane := (d.rate() / laneSize) - 1 - d.a[padCloseLane] ^= 0x8000000000000000 -} - -// finalize prepares the hash to output data by padding and one final permutation of the state. -func (d *digest) finalize() { - d.pad() - keccakF(&d.a) -} - -// squeeze outputs an arbitrary number of bytes from the hash state. -// Squeezing can require multiple calls to the F function (one per rate() bytes squeezed), -// although this is not the case for standard SHA3 parameters. This implementation only supports -// squeezing a single time, subsequent squeezes may lose alignment. Future implementations -// may wish to support multiple squeeze calls, for example to support use as a PRNG. -func (d *digest) squeeze(in []byte, toSqueeze int) []byte { - // Because we read in blocks of laneSize, we need enough room to read - // an integral number of lanes - needed := toSqueeze + (laneSize-toSqueeze%laneSize)%laneSize - if cap(in)-len(in) < needed { - newIn := make([]byte, len(in), len(in)+needed) - copy(newIn, in) - in = newIn - } - out := in[len(in) : len(in)+needed] - - for len(out) > 0 { - for i := 0; i < d.rate() && len(out) > 0; i += laneSize { - binary.LittleEndian.PutUint64(out[:], d.a[i/laneSize]) - out = out[laneSize:] - } - if len(out) > 0 { - keccakF(&d.a) - } - } - return in[:len(in)+toSqueeze] // Re-slice in case we wrote extra data. -} - -// Sum applies padding to the hash state and then squeezes out the desired nubmer of output bytes. -func (d *digest) Sum(in []byte) []byte { - // Make a copy of the original hash so that caller can keep writing and summing. - dup := *d - dup.finalize() - return dup.squeeze(in, dup.outputSize) -} - -// The NewKeccakX constructors enable initializing a hash in any of the four recommend sizes -// from the Keccak specification, all of which set capacity=2*outputSize. Note that the final -// NIST standard for SHA3 may specify different input/output lengths. -// The output size is indicated in bits but converted into bytes internally. -func NewKeccak224() hash.Hash { return &digest{outputSize: 224 / 8, capacity: 2 * 224 / 8} } -func NewKeccak256() hash.Hash { return &digest{outputSize: 256 / 8, capacity: 2 * 256 / 8} } -func NewKeccak384() hash.Hash { return &digest{outputSize: 384 / 8, capacity: 2 * 384 / 8} } -func NewKeccak512() hash.Hash { return &digest{outputSize: 512 / 8, capacity: 2 * 512 / 8} } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3_test.go deleted file mode 100644 index 05e7c958..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/sha3/sha3_test.go +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// These tests are a subset of those provided by the Keccak web site(http://keccak.noekeon.org/). - -import ( - "bytes" - "encoding/hex" - "fmt" - "hash" - "strings" - "testing" -) - -// testDigests maintains a digest state of each standard type. -var testDigests = map[string]*digest{ - "Keccak224": {outputSize: 224 / 8, capacity: 2 * 224 / 8}, - "Keccak256": {outputSize: 256 / 8, capacity: 2 * 256 / 8}, - "Keccak384": {outputSize: 384 / 8, capacity: 2 * 384 / 8}, - "Keccak512": {outputSize: 512 / 8, capacity: 2 * 512 / 8}, -} - -// testVector represents a test input and expected outputs from multiple algorithm variants. -type testVector struct { - desc string - input []byte - repeat int // input will be concatenated the input this many times. - want map[string]string -} - -// decodeHex converts an hex-encoded string into a raw byte string. -func decodeHex(s string) []byte { - b, err := hex.DecodeString(s) - if err != nil { - panic(err) - } - return b -} - -// shortTestVectors stores a series of short testVectors. -// Inputs of 8, 248, and 264 bits from http://keccak.noekeon.org/ are included below. -// The standard defines additional test inputs of all sizes between 0 and 2047 bits. -// Because the current implementation can only handle an integral number of bytes, -// most of the standard test inputs can't be used. -var shortKeccakTestVectors = []testVector{ - { - desc: "short-8b", - input: decodeHex("CC"), - repeat: 1, - want: map[string]string{ - "Keccak224": "A9CAB59EB40A10B246290F2D6086E32E3689FAF1D26B470C899F2802", - "Keccak256": "EEAD6DBFC7340A56CAEDC044696A168870549A6A7F6F56961E84A54BD9970B8A", - "Keccak384": "1B84E62A46E5A201861754AF5DC95C4A1A69CAF4A796AE405680161E29572641F5FA1E8641D7958336EE7B11C58F73E9", - "Keccak512": "8630C13CBD066EA74BBE7FE468FEC1DEE10EDC1254FB4C1B7C5FD69B646E44160B8CE01D05A0908CA790DFB080F4B513BC3B6225ECE7A810371441A5AC666EB9", - }, - }, - { - desc: "short-248b", - input: decodeHex("84FB51B517DF6C5ACCB5D022F8F28DA09B10232D42320FFC32DBECC3835B29"), - repeat: 1, - want: map[string]string{ - "Keccak224": "81AF3A7A5BD4C1F948D6AF4B96F93C3B0CF9C0E7A6DA6FCD71EEC7F6", - "Keccak256": "D477FB02CAAA95B3280EC8EE882C29D9E8A654B21EF178E0F97571BF9D4D3C1C", - "Keccak384": "503DCAA4ADDA5A9420B2E436DD62D9AB2E0254295C2982EF67FCE40F117A2400AB492F7BD5D133C6EC2232268BC27B42", - "Keccak512": "9D8098D8D6EDBBAA2BCFC6FB2F89C3EAC67FEC25CDFE75AA7BD570A648E8C8945FF2EC280F6DCF73386109155C5BBC444C707BB42EAB873F5F7476657B1BC1A8", - }, - }, - { - desc: "short-264b", - input: decodeHex("DE8F1B3FAA4B7040ED4563C3B8E598253178E87E4D0DF75E4FF2F2DEDD5A0BE046"), - repeat: 1, - want: map[string]string{ - "Keccak224": "F217812E362EC64D4DC5EACFABC165184BFA456E5C32C2C7900253D0", - "Keccak256": "E78C421E6213AFF8DE1F025759A4F2C943DB62BBDE359C8737E19B3776ED2DD2", - "Keccak384": "CF38764973F1EC1C34B5433AE75A3AAD1AAEF6AB197850C56C8617BCD6A882F6666883AC17B2DCCDBAA647075D0972B5", - "Keccak512": "9A7688E31AAF40C15575FC58C6B39267AAD3722E696E518A9945CF7F7C0FEA84CB3CB2E9F0384A6B5DC671ADE7FB4D2B27011173F3EEEAF17CB451CF26542031", - }, - }, -} - -// longTestVectors stores longer testVectors (currently only one). -// The computed test vector is 64 MiB long and is a truncated version of the -// ExtremelyLongMsgKAT taken from http://keccak.noekeon.org/. -var longKeccakTestVectors = []testVector{ - { - desc: "long-64MiB", - input: []byte("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno"), - repeat: 1024 * 1024, - want: map[string]string{ - "Keccak224": "50E35E40980FEEFF1EA490957B0E970257F75EA0D410EE0F0B8A7A58", - "Keccak256": "5015A4935F0B51E091C6550A94DCD262C08998232CCAA22E7F0756DEAC0DC0D0", - "Keccak384": "7907A8D0FAA7BC6A90FE14C6C958C956A0877E751455D8F13ACDB96F144B5896E716C06EC0CB56557A94EF5C3355F6F3", - "Keccak512": "3EC327D6759F769DEB74E80CA70C831BC29CAB048A4BF4190E4A1DD5C6507CF2B4B58937FDE81D36014E7DFE1B1DD8B0F27CB7614F9A645FEC114F1DAAEFC056", - }, - }, -} - -// TestKeccakVectors checks that correct output is produced for a set of known testVectors. -func TestKeccakVectors(t *testing.T) { - testCases := append([]testVector{}, shortKeccakTestVectors...) - if !testing.Short() { - testCases = append(testCases, longKeccakTestVectors...) - } - for _, tc := range testCases { - for alg, want := range tc.want { - d := testDigests[alg] - d.Reset() - for i := 0; i < tc.repeat; i++ { - d.Write(tc.input) - } - got := strings.ToUpper(hex.EncodeToString(d.Sum(nil))) - if got != want { - t.Errorf("%s, alg=%s\ngot %q, want %q", tc.desc, alg, got, want) - } - } - } -} - -// dumpState is a debugging function to pretty-print the internal state of the hash. -func (d *digest) dumpState() { - fmt.Printf("SHA3 hash, %d B output, %d B capacity (%d B rate)\n", d.outputSize, d.capacity, d.rate()) - fmt.Printf("Internal state after absorbing %d B:\n", d.absorbed) - - for x := 0; x < sliceSize; x++ { - for y := 0; y < sliceSize; y++ { - fmt.Printf("%v, ", d.a[x*sliceSize+y]) - } - fmt.Println("") - } -} - -// TestUnalignedWrite tests that writing data in an arbitrary pattern with small input buffers. -func TestUnalignedWrite(t *testing.T) { - buf := sequentialBytes(0x10000) - for alg, d := range testDigests { - d.Reset() - d.Write(buf) - want := d.Sum(nil) - d.Reset() - for i := 0; i < len(buf); { - // Cycle through offsets which make a 137 byte sequence. - // Because 137 is prime this sequence should exercise all corner cases. - offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} - for _, j := range offsets { - j = minInt(j, len(buf)-i) - d.Write(buf[i : i+j]) - i += j - } - } - got := d.Sum(nil) - if !bytes.Equal(got, want) { - t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) - } - } -} - -func TestAppend(t *testing.T) { - d := NewKeccak224() - - for capacity := 2; capacity < 64; capacity += 64 { - // The first time around the loop, Sum will have to reallocate. - // The second time, it will not. - buf := make([]byte, 2, capacity) - d.Reset() - d.Write([]byte{0xcc}) - buf = d.Sum(buf) - expected := "0000A9CAB59EB40A10B246290F2D6086E32E3689FAF1D26B470C899F2802" - if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { - t.Errorf("got %s, want %s", got, expected) - } - } -} - -func TestAppendNoRealloc(t *testing.T) { - buf := make([]byte, 1, 200) - d := NewKeccak224() - d.Write([]byte{0xcc}) - buf = d.Sum(buf) - expected := "00A9CAB59EB40A10B246290F2D6086E32E3689FAF1D26B470C899F2802" - if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { - t.Errorf("got %s, want %s", got, expected) - } -} - -// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. -func sequentialBytes(size int) []byte { - result := make([]byte, size) - for i := range result { - result[i] = byte(i) - } - return result -} - -// benchmarkBlockWrite tests the speed of writing data and never calling the permutation function. -func benchmarkBlockWrite(b *testing.B, d *digest) { - b.StopTimer() - d.Reset() - // Write all but the last byte of a block, to ensure that the permutation is not called. - data := sequentialBytes(d.rate() - 1) - b.SetBytes(int64(len(data))) - b.StartTimer() - for i := 0; i < b.N; i++ { - d.absorbed = 0 // Reset absorbed to avoid ever calling the permutation function - d.Write(data) - } - b.StopTimer() - d.Reset() -} - -// BenchmarkPermutationFunction measures the speed of the permutation function with no input data. -func BenchmarkPermutationFunction(b *testing.B) { - b.SetBytes(int64(stateSize)) - var lanes [numLanes]uint64 - for i := 0; i < b.N; i++ { - keccakF(&lanes) - } -} - -// BenchmarkSingleByteWrite tests the latency from writing a single byte -func BenchmarkSingleByteWrite(b *testing.B) { - b.StopTimer() - d := testDigests["Keccak512"] - d.Reset() - data := sequentialBytes(1) //1 byte buffer - b.SetBytes(int64(d.rate()) - 1) - b.StartTimer() - for i := 0; i < b.N; i++ { - d.absorbed = 0 // Reset absorbed to avoid ever calling the permutation function - - // Write all but the last byte of a block, one byte at a time. - for j := 0; j < d.rate()-1; j++ { - d.Write(data) - } - } - b.StopTimer() - d.Reset() -} - -// BenchmarkSingleByteX measures the block write speed for each size of the digest. -func BenchmarkBlockWrite512(b *testing.B) { benchmarkBlockWrite(b, testDigests["Keccak512"]) } -func BenchmarkBlockWrite384(b *testing.B) { benchmarkBlockWrite(b, testDigests["Keccak384"]) } -func BenchmarkBlockWrite256(b *testing.B) { benchmarkBlockWrite(b, testDigests["Keccak256"]) } -func BenchmarkBlockWrite224(b *testing.B) { benchmarkBlockWrite(b, testDigests["Keccak224"]) } - -// benchmarkBulkHash tests the speed to hash a 16 KiB buffer. -func benchmarkBulkHash(b *testing.B, h hash.Hash) { - b.StopTimer() - h.Reset() - size := 1 << 14 - data := sequentialBytes(size) - b.SetBytes(int64(size)) - b.StartTimer() - - var digest []byte - for i := 0; i < b.N; i++ { - h.Write(data) - digest = h.Sum(digest[:0]) - } - b.StopTimer() - h.Reset() -} - -// benchmarkBulkKeccakX test the speed to hash a 16 KiB buffer by calling benchmarkBulkHash. -func BenchmarkBulkKeccak512(b *testing.B) { benchmarkBulkHash(b, NewKeccak512()) } -func BenchmarkBulkKeccak384(b *testing.B) { benchmarkBulkHash(b, NewKeccak384()) } -func BenchmarkBulkKeccak256(b *testing.B) { benchmarkBulkHash(b, NewKeccak256()) } -func BenchmarkBulkKeccak224(b *testing.B) { benchmarkBulkHash(b, NewKeccak224()) } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/agent.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/agent.go deleted file mode 100644 index a91da34c..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/agent.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "encoding/base64" - "errors" - "io" - "sync" -) - -// See [PROTOCOL.agent], section 3. -const ( - // 3.2 Requests from client to agent for protocol 2 key operations - agentRequestIdentities = 11 - agentSignRequest = 13 - agentAddIdentity = 17 - agentRemoveIdentity = 18 - agentRemoveAllIdentities = 19 - agentAddIdConstrained = 25 - - // 3.3 Key-type independent requests from client to agent - agentAddSmartcardKey = 20 - agentRemoveSmartcardKey = 21 - agentLock = 22 - agentUnlock = 23 - agentAddSmartcardKeyConstrained = 26 - - // 3.4 Generic replies from agent to client - agentFailure = 5 - agentSuccess = 6 - - // 3.6 Replies from agent to client for protocol 2 key operations - agentIdentitiesAnswer = 12 - agentSignResponse = 14 - - // 3.7 Key constraint identifiers - agentConstrainLifetime = 1 - agentConstrainConfirm = 2 -) - -// maxAgentResponseBytes is the maximum agent reply size that is accepted. This -// is a sanity check, not a limit in the spec. -const maxAgentResponseBytes = 16 << 20 - -// Agent messages: -// These structures mirror the wire format of the corresponding ssh agent -// messages found in [PROTOCOL.agent]. - -type failureAgentMsg struct{} - -type successAgentMsg struct{} - -// See [PROTOCOL.agent], section 2.5.2. -type requestIdentitiesAgentMsg struct{} - -// See [PROTOCOL.agent], section 2.5.2. -type identitiesAnswerAgentMsg struct { - NumKeys uint32 - Keys []byte `ssh:"rest"` -} - -// See [PROTOCOL.agent], section 2.6.2. -type signRequestAgentMsg struct { - KeyBlob []byte - Data []byte - Flags uint32 -} - -// See [PROTOCOL.agent], section 2.6.2. -type signResponseAgentMsg struct { - SigBlob []byte -} - -// AgentKey represents a protocol 2 key as defined in [PROTOCOL.agent], -// section 2.5.2. -type AgentKey struct { - blob []byte - Comment string -} - -// String returns the storage form of an agent key with the format, base64 -// encoded serialized key, and the comment if it is not empty. -func (ak *AgentKey) String() string { - algo, _, ok := parseString(ak.blob) - if !ok { - return "ssh: malformed key" - } - - s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob) - - if ak.Comment != "" { - s += " " + ak.Comment - } - - return s -} - -// Key returns an agent's public key as one of the supported key or certificate types. -func (ak *AgentKey) Key() (PublicKey, error) { - if key, _, ok := ParsePublicKey(ak.blob); ok { - return key, nil - } - return nil, errors.New("ssh: failed to parse key blob") -} - -func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) { - ak := new(AgentKey) - - if ak.blob, in, ok = parseString(in); !ok { - return - } - - comment, in, ok := parseString(in) - if !ok { - return - } - ak.Comment = string(comment) - - return ak, in, true -} - -// AgentClient provides a means to communicate with an ssh agent process based -// on the protocol described in [PROTOCOL.agent]?rev=1.6. -type AgentClient struct { - // conn is typically represented by using a *net.UnixConn - conn io.ReadWriter - // mu is used to prevent concurrent access to the agent - mu sync.Mutex -} - -// NewAgentClient creates and returns a new *AgentClient using the -// passed in io.ReadWriter as a connection to a ssh agent. -func NewAgentClient(rw io.ReadWriter) *AgentClient { - return &AgentClient{conn: rw} -} - -// sendAndReceive sends req to the agent and waits for a reply. On success, -// the reply is unmarshaled into reply and replyType is set to the first byte of -// the reply, which contains the type of the message. -func (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) { - // ac.mu prevents multiple, concurrent requests. Since the agent is typically - // on the same machine, we don't attempt to pipeline the requests. - ac.mu.Lock() - defer ac.mu.Unlock() - - msg := make([]byte, stringLength(len(req))) - marshalString(msg, req) - if _, err = ac.conn.Write(msg); err != nil { - return - } - - var respSizeBuf [4]byte - if _, err = io.ReadFull(ac.conn, respSizeBuf[:]); err != nil { - return - } - respSize, _, _ := parseUint32(respSizeBuf[:]) - - if respSize > maxAgentResponseBytes { - err = errors.New("ssh: agent reply too large") - return - } - - buf := make([]byte, respSize) - if _, err = io.ReadFull(ac.conn, buf); err != nil { - return - } - return unmarshalAgentMsg(buf) -} - -// RequestIdentities queries the agent for protocol 2 keys as defined in -// [PROTOCOL.agent] section 2.5.2. -func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) { - req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{}) - - msg, msgType, err := ac.sendAndReceive(req) - if err != nil { - return nil, err - } - - switch msg := msg.(type) { - case *identitiesAnswerAgentMsg: - if msg.NumKeys > maxAgentResponseBytes/8 { - return nil, errors.New("ssh: too many keys in agent reply") - } - keys := make([]*AgentKey, msg.NumKeys) - data := msg.Keys - for i := uint32(0); i < msg.NumKeys; i++ { - var key *AgentKey - var ok bool - if key, data, ok = parseAgentKey(data); !ok { - return nil, ParseError{agentIdentitiesAnswer} - } - keys[i] = key - } - return keys, nil - case *failureAgentMsg: - return nil, errors.New("ssh: failed to list keys") - } - return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType} -} - -// SignRequest requests the signing of data by the agent using a protocol 2 key -// as defined in [PROTOCOL.agent] section 2.6.2. -func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) { - req := marshal(agentSignRequest, signRequestAgentMsg{ - KeyBlob: MarshalPublicKey(key), - Data: data, - }) - - msg, msgType, err := ac.sendAndReceive(req) - if err != nil { - return nil, err - } - - switch msg := msg.(type) { - case *signResponseAgentMsg: - return msg.SigBlob, nil - case *failureAgentMsg: - return nil, errors.New("ssh: failed to sign challenge") - } - return nil, UnexpectedMessageError{agentSignResponse, msgType} -} - -// unmarshalAgentMsg parses an agent message in packet, returning the parsed -// form and the message type of packet. -func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) { - if len(packet) < 1 { - return nil, 0, ParseError{0} - } - var msg interface{} - switch packet[0] { - case agentFailure: - msg = new(failureAgentMsg) - case agentSuccess: - msg = new(successAgentMsg) - case agentIdentitiesAnswer: - msg = new(identitiesAnswerAgentMsg) - case agentSignResponse: - msg = new(signResponseAgentMsg) - default: - return nil, 0, UnexpectedMessageError{0, packet[0]} - } - if err := unmarshal(msg, packet, packet[0]); err != nil { - return nil, 0, err - } - return msg, packet[0], nil -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs.go deleted file mode 100644 index d958f310..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs.go +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "time" -) - -// These constants from [PROTOCOL.certkeys] represent the algorithm names -// for certificate types supported by this package. -const ( - CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" - CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" - CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" - CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" - CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" -) - -// Certificate types are used to specify whether a certificate is for identification -// of a user or a host. Current identities are defined in [PROTOCOL.certkeys]. -const ( - UserCert = 1 - HostCert = 2 -) - -type signature struct { - Format string - Blob []byte -} - -type tuple struct { - Name string - Data string -} - -const ( - maxUint64 = 1<<64 - 1 - maxInt64 = 1<<63 - 1 -) - -// CertTime represents an unsigned 64-bit time value in seconds starting from -// UNIX epoch. We use CertTime instead of time.Time in order to properly handle -// the "infinite" time value ^0, which would become negative when expressed as -// an int64. -type CertTime uint64 - -func (ct CertTime) Time() time.Time { - if ct > maxInt64 { - return time.Unix(maxInt64, 0) - } - return time.Unix(int64(ct), 0) -} - -func (ct CertTime) IsInfinite() bool { - return ct == maxUint64 -} - -// An OpenSSHCertV01 represents an OpenSSH certificate as defined in -// [PROTOCOL.certkeys]?rev=1.8. -type OpenSSHCertV01 struct { - Nonce []byte - Key PublicKey - Serial uint64 - Type uint32 - KeyId string - ValidPrincipals []string - ValidAfter, ValidBefore CertTime - CriticalOptions []tuple - Extensions []tuple - Reserved []byte - SignatureKey PublicKey - Signature *signature -} - -// validateOpenSSHCertV01Signature uses the cert's SignatureKey to verify that -// the cert's Signature.Blob is the result of signing the cert bytes starting -// from the algorithm string and going up to and including the SignatureKey. -func validateOpenSSHCertV01Signature(cert *OpenSSHCertV01) bool { - return cert.SignatureKey.Verify(cert.BytesForSigning(), cert.Signature.Blob) -} - -var certAlgoNames = map[string]string{ - KeyAlgoRSA: CertAlgoRSAv01, - KeyAlgoDSA: CertAlgoDSAv01, - KeyAlgoECDSA256: CertAlgoECDSA256v01, - KeyAlgoECDSA384: CertAlgoECDSA384v01, - KeyAlgoECDSA521: CertAlgoECDSA521v01, -} - -// certToPrivAlgo returns the underlying algorithm for a certificate algorithm. -// Panics if a non-certificate algorithm is passed. -func certToPrivAlgo(algo string) string { - for privAlgo, pubAlgo := range certAlgoNames { - if pubAlgo == algo { - return privAlgo - } - } - panic("unknown cert algorithm") -} - -func (cert *OpenSSHCertV01) marshal(includeAlgo, includeSig bool) []byte { - algoName := cert.PublicKeyAlgo() - pubKey := cert.Key.Marshal() - sigKey := MarshalPublicKey(cert.SignatureKey) - - var length int - if includeAlgo { - length += stringLength(len(algoName)) - } - length += stringLength(len(cert.Nonce)) - length += len(pubKey) - length += 8 // Length of Serial - length += 4 // Length of Type - length += stringLength(len(cert.KeyId)) - length += lengthPrefixedNameListLength(cert.ValidPrincipals) - length += 8 // Length of ValidAfter - length += 8 // Length of ValidBefore - length += tupleListLength(cert.CriticalOptions) - length += tupleListLength(cert.Extensions) - length += stringLength(len(cert.Reserved)) - length += stringLength(len(sigKey)) - if includeSig { - length += signatureLength(cert.Signature) - } - - ret := make([]byte, length) - r := ret - if includeAlgo { - r = marshalString(r, []byte(algoName)) - } - r = marshalString(r, cert.Nonce) - copy(r, pubKey) - r = r[len(pubKey):] - r = marshalUint64(r, cert.Serial) - r = marshalUint32(r, cert.Type) - r = marshalString(r, []byte(cert.KeyId)) - r = marshalLengthPrefixedNameList(r, cert.ValidPrincipals) - r = marshalUint64(r, uint64(cert.ValidAfter)) - r = marshalUint64(r, uint64(cert.ValidBefore)) - r = marshalTupleList(r, cert.CriticalOptions) - r = marshalTupleList(r, cert.Extensions) - r = marshalString(r, cert.Reserved) - r = marshalString(r, sigKey) - if includeSig { - r = marshalSignature(r, cert.Signature) - } - if len(r) > 0 { - panic("ssh: internal error, marshaling certificate did not fill the entire buffer") - } - return ret -} - -func (cert *OpenSSHCertV01) BytesForSigning() []byte { - return cert.marshal(true, false) -} - -func (cert *OpenSSHCertV01) Marshal() []byte { - return cert.marshal(false, true) -} - -func (c *OpenSSHCertV01) PublicKeyAlgo() string { - algo, ok := certAlgoNames[c.Key.PublicKeyAlgo()] - if !ok { - panic("unknown cert key type") - } - return algo -} - -func (c *OpenSSHCertV01) PrivateKeyAlgo() string { - return c.Key.PrivateKeyAlgo() -} - -func (c *OpenSSHCertV01) Verify(data []byte, sig []byte) bool { - return c.Key.Verify(data, sig) -} - -func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) { - cert := new(OpenSSHCertV01) - - if cert.Nonce, in, ok = parseString(in); !ok { - return - } - - privAlgo := certToPrivAlgo(algo) - cert.Key, in, ok = parsePubKey(in, privAlgo) - if !ok { - return - } - - // We test PublicKeyAlgo to make sure we don't use some weird sub-cert. - if cert.Key.PublicKeyAlgo() != privAlgo { - ok = false - return - } - - if cert.Serial, in, ok = parseUint64(in); !ok { - return - } - - if cert.Type, in, ok = parseUint32(in); !ok { - return - } - - keyId, in, ok := parseString(in) - if !ok { - return - } - cert.KeyId = string(keyId) - - if cert.ValidPrincipals, in, ok = parseLengthPrefixedNameList(in); !ok { - return - } - - va, in, ok := parseUint64(in) - if !ok { - return - } - cert.ValidAfter = CertTime(va) - - vb, in, ok := parseUint64(in) - if !ok { - return - } - cert.ValidBefore = CertTime(vb) - - if cert.CriticalOptions, in, ok = parseTupleList(in); !ok { - return - } - - if cert.Extensions, in, ok = parseTupleList(in); !ok { - return - } - - if cert.Reserved, in, ok = parseString(in); !ok { - return - } - - sigKey, in, ok := parseString(in) - if !ok { - return - } - if cert.SignatureKey, _, ok = ParsePublicKey(sigKey); !ok { - return - } - - if cert.Signature, in, ok = parseSignature(in); !ok { - return - } - - ok = true - return cert, in, ok -} - -func lengthPrefixedNameListLength(namelist []string) int { - length := 4 // length prefix for list - for _, name := range namelist { - length += 4 // length prefix for name - length += len(name) - } - return length -} - -func marshalLengthPrefixedNameList(to []byte, namelist []string) []byte { - length := uint32(lengthPrefixedNameListLength(namelist) - 4) - to = marshalUint32(to, length) - for _, name := range namelist { - to = marshalString(to, []byte(name)) - } - return to -} - -func parseLengthPrefixedNameList(in []byte) (out []string, rest []byte, ok bool) { - list, rest, ok := parseString(in) - if !ok { - return - } - - for len(list) > 0 { - var next []byte - if next, list, ok = parseString(list); !ok { - return nil, nil, false - } - out = append(out, string(next)) - } - ok = true - return -} - -func tupleListLength(tupleList []tuple) int { - length := 4 // length prefix for list - for _, t := range tupleList { - length += 4 // length prefix for t.Name - length += len(t.Name) - length += 4 // length prefix for t.Data - length += len(t.Data) - } - return length -} - -func marshalTupleList(to []byte, tuplelist []tuple) []byte { - length := uint32(tupleListLength(tuplelist) - 4) - to = marshalUint32(to, length) - for _, t := range tuplelist { - to = marshalString(to, []byte(t.Name)) - to = marshalString(to, []byte(t.Data)) - } - return to -} - -func parseTupleList(in []byte) (out []tuple, rest []byte, ok bool) { - list, rest, ok := parseString(in) - if !ok { - return - } - - for len(list) > 0 { - var name, data []byte - var ok bool - name, list, ok = parseString(list) - if !ok { - return nil, nil, false - } - data, list, ok = parseString(list) - if !ok { - return nil, nil, false - } - out = append(out, tuple{string(name), string(data)}) - } - ok = true - return -} - -func signatureLength(sig *signature) int { - length := 4 // length prefix for signature - length += stringLength(len(sig.Format)) - length += stringLength(len(sig.Blob)) - return length -} - -func marshalSignature(to []byte, sig *signature) []byte { - length := uint32(signatureLength(sig) - 4) - to = marshalUint32(to, length) - to = marshalString(to, []byte(sig.Format)) - to = marshalString(to, sig.Blob) - return to -} - -func parseSignatureBody(in []byte) (out *signature, rest []byte, ok bool) { - var format []byte - if format, in, ok = parseString(in); !ok { - return - } - - out = &signature{ - Format: string(format), - } - - if out.Blob, in, ok = parseString(in); !ok { - return - } - - return out, in, ok -} - -func parseSignature(in []byte) (out *signature, rest []byte, ok bool) { - var sigBytes []byte - if sigBytes, rest, ok = parseString(in); !ok { - return - } - - out, sigBytes, ok = parseSignatureBody(sigBytes) - if !ok || len(sigBytes) > 0 { - return nil, nil, false - } - return -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs_test.go deleted file mode 100644 index 3cec28ec..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/certs_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "testing" -) - -// Cert generated by ssh-keygen 6.0p1 Debian-4. -// % ssh-keygen -s ca-key -I test user-key -var exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=` - -func TestParseCert(t *testing.T) { - authKeyBytes := []byte(exampleSSHCert) - - key, _, _, rest, ok := ParseAuthorizedKey(authKeyBytes) - if !ok { - t.Fatalf("could not parse certificate") - } - if len(rest) > 0 { - t.Errorf("rest: got %q, want empty", rest) - } - - if _, ok = key.(*OpenSSHCertV01); !ok { - t.Fatalf("got %#v, want *OpenSSHCertV01", key) - } - - marshaled := MarshalAuthorizedKey(key) - // Before comparison, remove the trailing newline that - // MarshalAuthorizedKey adds. - marshaled = marshaled[:len(marshaled)-1] - if !bytes.Equal(authKeyBytes, marshaled) { - t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes) - } -} - -func TestVerifyCert(t *testing.T) { - key, _, _, _, _ := ParseAuthorizedKey([]byte(exampleSSHCert)) - validCert := key.(*OpenSSHCertV01) - if ok := validateOpenSSHCertV01Signature(validCert); !ok { - t.Error("Unable to validate certificate!") - } - - invalidCert := &OpenSSHCertV01{ - Key: rsaKey.PublicKey(), - SignatureKey: ecdsaKey.PublicKey(), - Signature: &signature{}, - } - if ok := validateOpenSSHCertV01Signature(invalidCert); ok { - t.Error("Invalid cert signature passed validation!") - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/channel.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/channel.go deleted file mode 100644 index c5413c9f..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/channel.go +++ /dev/null @@ -1,594 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "errors" - "fmt" - "io" - "sync" - "sync/atomic" -) - -// extendedDataTypeCode identifies an OpenSSL extended data type. See RFC 4254, -// section 5.2. -type extendedDataTypeCode uint32 - -const ( - // extendedDataStderr is the extended data type that is used for stderr. - extendedDataStderr extendedDataTypeCode = 1 - - // minPacketLength defines the smallest valid packet - minPacketLength = 9 - - // channelMaxPacketSize defines the maximum packet size advertised in open messages - channelMaxPacketSize = 1 << 15 // RFC 4253 6.1, minimum 32 KiB - - // channelWindowSize defines the window size advertised in open messages - channelWindowSize = 64 * channelMaxPacketSize // Like OpenSSH -) - -// A Channel is an ordered, reliable, duplex stream that is multiplexed over an -// SSH connection. Channel.Read can return a ChannelRequest as an error. -type Channel interface { - // Accept accepts the channel creation request. - Accept() error - // Reject rejects the channel creation request. After calling this, no - // other methods on the Channel may be called. If they are then the - // peer is likely to signal a protocol error and drop the connection. - Reject(reason RejectionReason, message string) error - - // Read may return a ChannelRequest as an error. - Read(data []byte) (int, error) - Write(data []byte) (int, error) - Close() error - - // Stderr returns an io.Writer that writes to this channel with the - // extended data type set to stderr. - Stderr() io.Writer - - // AckRequest either sends an ack or nack to the channel request. - AckRequest(ok bool) error - - // ChannelType returns the type of the channel, as supplied by the - // client. - ChannelType() string - // ExtraData returns the arbitrary payload for this channel, as supplied - // by the client. This data is specific to the channel type. - ExtraData() []byte -} - -// ChannelRequest represents a request sent on a channel, outside of the normal -// stream of bytes. It may result from calling Read on a Channel. -type ChannelRequest struct { - Request string - WantReply bool - Payload []byte -} - -func (c ChannelRequest) Error() string { - return "ssh: channel request received" -} - -// RejectionReason is an enumeration used when rejecting channel creation -// requests. See RFC 4254, section 5.1. -type RejectionReason uint32 - -const ( - Prohibited RejectionReason = iota + 1 - ConnectionFailed - UnknownChannelType - ResourceShortage -) - -// String converts the rejection reason to human readable form. -func (r RejectionReason) String() string { - switch r { - case Prohibited: - return "administratively prohibited" - case ConnectionFailed: - return "connect failed" - case UnknownChannelType: - return "unknown channel type" - case ResourceShortage: - return "resource shortage" - } - return fmt.Sprintf("unknown reason %d", int(r)) -} - -type channel struct { - packetConn // the underlying transport - localId, remoteId uint32 - remoteWin window - maxPacket uint32 - isClosed uint32 // atomic bool, non zero if true -} - -func (c *channel) sendWindowAdj(n int) error { - msg := windowAdjustMsg{ - PeersId: c.remoteId, - AdditionalBytes: uint32(n), - } - return c.writePacket(marshal(msgChannelWindowAdjust, msg)) -} - -// sendEOF sends EOF to the remote side. RFC 4254 Section 5.3 -func (c *channel) sendEOF() error { - return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{ - PeersId: c.remoteId, - })) -} - -// sendClose informs the remote side of our intent to close the channel. -func (c *channel) sendClose() error { - return c.packetConn.writePacket(marshal(msgChannelClose, channelCloseMsg{ - PeersId: c.remoteId, - })) -} - -func (c *channel) sendChannelOpenFailure(reason RejectionReason, message string) error { - reject := channelOpenFailureMsg{ - PeersId: c.remoteId, - Reason: reason, - Message: message, - Language: "en", - } - return c.writePacket(marshal(msgChannelOpenFailure, reject)) -} - -func (c *channel) writePacket(b []byte) error { - if c.closed() { - return io.EOF - } - if uint32(len(b)) > c.maxPacket { - return fmt.Errorf("ssh: cannot write %d bytes, maxPacket is %d bytes", len(b), c.maxPacket) - } - return c.packetConn.writePacket(b) -} - -func (c *channel) closed() bool { - return atomic.LoadUint32(&c.isClosed) > 0 -} - -func (c *channel) setClosed() bool { - return atomic.CompareAndSwapUint32(&c.isClosed, 0, 1) -} - -type serverChan struct { - channel - // immutable once created - chanType string - extraData []byte - - serverConn *ServerConn - myWindow uint32 - theyClosed bool // indicates the close msg has been received from the remote side - theySentEOF bool - isDead uint32 - err error - - pendingRequests []ChannelRequest - pendingData []byte - head, length int - - // This lock is inferior to serverConn.lock - cond *sync.Cond -} - -func (c *serverChan) Accept() error { - c.serverConn.lock.Lock() - defer c.serverConn.lock.Unlock() - - if c.serverConn.err != nil { - return c.serverConn.err - } - - confirm := channelOpenConfirmMsg{ - PeersId: c.remoteId, - MyId: c.localId, - MyWindow: c.myWindow, - MaxPacketSize: c.maxPacket, - } - return c.writePacket(marshal(msgChannelOpenConfirm, confirm)) -} - -func (c *serverChan) Reject(reason RejectionReason, message string) error { - c.serverConn.lock.Lock() - defer c.serverConn.lock.Unlock() - - if c.serverConn.err != nil { - return c.serverConn.err - } - - return c.sendChannelOpenFailure(reason, message) -} - -func (c *serverChan) handlePacket(packet interface{}) { - c.cond.L.Lock() - defer c.cond.L.Unlock() - - switch packet := packet.(type) { - case *channelRequestMsg: - req := ChannelRequest{ - Request: packet.Request, - WantReply: packet.WantReply, - Payload: packet.RequestSpecificData, - } - - c.pendingRequests = append(c.pendingRequests, req) - c.cond.Signal() - case *channelCloseMsg: - c.theyClosed = true - c.cond.Signal() - case *channelEOFMsg: - c.theySentEOF = true - c.cond.Signal() - case *windowAdjustMsg: - if !c.remoteWin.add(packet.AdditionalBytes) { - panic("illegal window update") - } - default: - panic("unknown packet type") - } -} - -func (c *serverChan) handleData(data []byte) { - c.cond.L.Lock() - defer c.cond.L.Unlock() - - // The other side should never send us more than our window. - if len(data)+c.length > len(c.pendingData) { - // TODO(agl): we should tear down the channel with a protocol - // error. - return - } - - c.myWindow -= uint32(len(data)) - for i := 0; i < 2; i++ { - tail := c.head + c.length - if tail >= len(c.pendingData) { - tail -= len(c.pendingData) - } - n := copy(c.pendingData[tail:], data) - data = data[n:] - c.length += n - } - - c.cond.Signal() -} - -func (c *serverChan) Stderr() io.Writer { - return extendedDataChannel{c: c, t: extendedDataStderr} -} - -// extendedDataChannel is an io.Writer that writes any data to c as extended -// data of the given type. -type extendedDataChannel struct { - t extendedDataTypeCode - c *serverChan -} - -func (edc extendedDataChannel) Write(data []byte) (n int, err error) { - const headerLength = 13 // 1 byte message type, 4 bytes remoteId, 4 bytes extended message type, 4 bytes data length - c := edc.c - for len(data) > 0 { - space := min(c.maxPacket-headerLength, len(data)) - if space, err = c.getWindowSpace(space); err != nil { - return 0, err - } - todo := data - if uint32(len(todo)) > space { - todo = todo[:space] - } - - packet := make([]byte, headerLength+len(todo)) - packet[0] = msgChannelExtendedData - marshalUint32(packet[1:], c.remoteId) - marshalUint32(packet[5:], uint32(edc.t)) - marshalUint32(packet[9:], uint32(len(todo))) - copy(packet[13:], todo) - - if err = c.writePacket(packet); err != nil { - return - } - - n += len(todo) - data = data[len(todo):] - } - - return -} - -func (c *serverChan) Read(data []byte) (n int, err error) { - n, err, windowAdjustment := c.read(data) - - if windowAdjustment > 0 { - packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{ - PeersId: c.remoteId, - AdditionalBytes: windowAdjustment, - }) - err = c.writePacket(packet) - } - - return -} - -func (c *serverChan) read(data []byte) (n int, err error, windowAdjustment uint32) { - c.cond.L.Lock() - defer c.cond.L.Unlock() - - if c.err != nil { - return 0, c.err, 0 - } - - for { - if c.theySentEOF || c.theyClosed || c.dead() { - return 0, io.EOF, 0 - } - - if len(c.pendingRequests) > 0 { - req := c.pendingRequests[0] - if len(c.pendingRequests) == 1 { - c.pendingRequests = nil - } else { - oldPendingRequests := c.pendingRequests - c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1) - copy(c.pendingRequests, oldPendingRequests[1:]) - } - - return 0, req, 0 - } - - if c.length > 0 { - tail := min(uint32(c.head+c.length), len(c.pendingData)) - n = copy(data, c.pendingData[c.head:tail]) - c.head += n - c.length -= n - if c.head == len(c.pendingData) { - c.head = 0 - } - - windowAdjustment = uint32(len(c.pendingData)-c.length) - c.myWindow - if windowAdjustment < uint32(len(c.pendingData)/2) { - windowAdjustment = 0 - } - c.myWindow += windowAdjustment - - return - } - - c.cond.Wait() - } - - panic("unreachable") -} - -// getWindowSpace takes, at most, max bytes of space from the peer's window. It -// returns the number of bytes actually reserved. -func (c *serverChan) getWindowSpace(max uint32) (uint32, error) { - if c.dead() || c.closed() { - return 0, io.EOF - } - return c.remoteWin.reserve(max), nil -} - -func (c *serverChan) dead() bool { - return atomic.LoadUint32(&c.isDead) > 0 -} - -func (c *serverChan) setDead() { - atomic.StoreUint32(&c.isDead, 1) -} - -func (c *serverChan) Write(data []byte) (n int, err error) { - const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length - for len(data) > 0 { - space := min(c.maxPacket-headerLength, len(data)) - if space, err = c.getWindowSpace(space); err != nil { - return 0, err - } - todo := data - if uint32(len(todo)) > space { - todo = todo[:space] - } - - packet := make([]byte, headerLength+len(todo)) - packet[0] = msgChannelData - marshalUint32(packet[1:], c.remoteId) - marshalUint32(packet[5:], uint32(len(todo))) - copy(packet[9:], todo) - - if err = c.writePacket(packet); err != nil { - return - } - - n += len(todo) - data = data[len(todo):] - } - - return -} - -// Close signals the intent to close the channel. -func (c *serverChan) Close() error { - c.serverConn.lock.Lock() - defer c.serverConn.lock.Unlock() - - if c.serverConn.err != nil { - return c.serverConn.err - } - - if !c.setClosed() { - return errors.New("ssh: channel already closed") - } - return c.sendClose() -} - -func (c *serverChan) AckRequest(ok bool) error { - c.serverConn.lock.Lock() - defer c.serverConn.lock.Unlock() - - if c.serverConn.err != nil { - return c.serverConn.err - } - - if !ok { - ack := channelRequestFailureMsg{ - PeersId: c.remoteId, - } - return c.writePacket(marshal(msgChannelFailure, ack)) - } - - ack := channelRequestSuccessMsg{ - PeersId: c.remoteId, - } - return c.writePacket(marshal(msgChannelSuccess, ack)) -} - -func (c *serverChan) ChannelType() string { - return c.chanType -} - -func (c *serverChan) ExtraData() []byte { - return c.extraData -} - -// A clientChan represents a single RFC 4254 channel multiplexed -// over a SSH connection. -type clientChan struct { - channel - stdin *chanWriter - stdout *chanReader - stderr *chanReader - msg chan interface{} -} - -// newClientChan returns a partially constructed *clientChan -// using the local id provided. To be usable clientChan.remoteId -// needs to be assigned once known. -func newClientChan(cc packetConn, id uint32) *clientChan { - c := &clientChan{ - channel: channel{ - packetConn: cc, - localId: id, - remoteWin: window{Cond: newCond()}, - }, - msg: make(chan interface{}, 16), - } - c.stdin = &chanWriter{ - channel: &c.channel, - } - c.stdout = &chanReader{ - channel: &c.channel, - buffer: newBuffer(), - } - c.stderr = &chanReader{ - channel: &c.channel, - buffer: newBuffer(), - } - return c -} - -// waitForChannelOpenResponse, if successful, fills out -// the remoteId and records any initial window advertisement. -func (c *clientChan) waitForChannelOpenResponse() error { - switch msg := (<-c.msg).(type) { - case *channelOpenConfirmMsg: - if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { - return errors.New("ssh: invalid MaxPacketSize from peer") - } - // fixup remoteId field - c.remoteId = msg.MyId - c.maxPacket = msg.MaxPacketSize - c.remoteWin.add(msg.MyWindow) - return nil - case *channelOpenFailureMsg: - return errors.New(safeString(msg.Message)) - } - return errors.New("ssh: unexpected packet") -} - -// Close signals the intent to close the channel. -func (c *clientChan) Close() error { - if !c.setClosed() { - return errors.New("ssh: channel already closed") - } - c.stdout.eof() - c.stderr.eof() - return c.sendClose() -} - -// A chanWriter represents the stdin of a remote process. -type chanWriter struct { - *channel - // indicates the writer has been closed. eof is owned by the - // caller of Write/Close. - eof bool -} - -// Write writes data to the remote process's standard input. -func (w *chanWriter) Write(data []byte) (written int, err error) { - const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length - for len(data) > 0 { - if w.eof || w.closed() { - err = io.EOF - return - } - // never send more data than maxPacket even if - // there is sufficient window. - n := min(w.maxPacket-headerLength, len(data)) - r := w.remoteWin.reserve(n) - n = r - remoteId := w.remoteId - packet := []byte{ - msgChannelData, - byte(remoteId >> 24), byte(remoteId >> 16), byte(remoteId >> 8), byte(remoteId), - byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n), - } - if err = w.writePacket(append(packet, data[:n]...)); err != nil { - break - } - data = data[n:] - written += int(n) - } - return -} - -func min(a uint32, b int) uint32 { - if a < uint32(b) { - return a - } - return uint32(b) -} - -func (w *chanWriter) Close() error { - w.eof = true - return w.sendEOF() -} - -// A chanReader represents stdout or stderr of a remote process. -type chanReader struct { - *channel // the channel backing this reader - *buffer -} - -// Read reads data from the remote process's stdout or stderr. -func (r *chanReader) Read(buf []byte) (int, error) { - n, err := r.buffer.Read(buf) - if err != nil { - if err == io.EOF { - return n, err - } - return 0, err - } - err = r.sendWindowAdj(n) - if err == io.EOF && n > 0 { - // sendWindowAdjust can return io.EOF if the remote peer has - // closed the connection, however we want to defer forwarding io.EOF to the - // caller of Read until the buffer has been drained. - err = nil - } - return n, err -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher.go deleted file mode 100644 index bc2e9838..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rc4" -) - -// streamDump is used to dump the initial keystream for stream ciphers. It is a -// a write-only buffer, and not intended for reading so do not require a mutex. -var streamDump [512]byte - -// noneCipher implements cipher.Stream and provides no encryption. It is used -// by the transport before the first key-exchange. -type noneCipher struct{} - -func (c noneCipher) XORKeyStream(dst, src []byte) { - copy(dst, src) -} - -func newAESCTR(key, iv []byte) (cipher.Stream, error) { - c, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return cipher.NewCTR(c, iv), nil -} - -func newRC4(key, iv []byte) (cipher.Stream, error) { - return rc4.NewCipher(key) -} - -type cipherMode struct { - keySize int - ivSize int - skip int - createFunc func(key, iv []byte) (cipher.Stream, error) -} - -func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) { - if len(key) < c.keySize { - panic("ssh: key length too small for cipher") - } - if len(iv) < c.ivSize { - panic("ssh: iv too small for cipher") - } - - stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize]) - if err != nil { - return nil, err - } - - for remainingToDump := c.skip; remainingToDump > 0; { - dumpThisTime := remainingToDump - if dumpThisTime > len(streamDump) { - dumpThisTime = len(streamDump) - } - stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) - remainingToDump -= dumpThisTime - } - - return stream, nil -} - -// Specifies a default set of ciphers and a preference order. This is based on -// OpenSSH's default client preference order, minus algorithms that are not -// implemented. -var DefaultCipherOrder = []string{ - "aes128-ctr", "aes192-ctr", "aes256-ctr", - "arcfour256", "arcfour128", -} - -// cipherModes documents properties of supported ciphers. Ciphers not included -// are not supported and will not be negotiated, even if explicitly requested in -// ClientConfig.Crypto.Ciphers. -var cipherModes = map[string]*cipherMode{ - // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms - // are defined in the order specified in the RFC. - "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR}, - "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR}, - "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR}, - - // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. - // They are defined in the order specified in the RFC. - "arcfour128": {16, 0, 1536, newRC4}, - "arcfour256": {32, 0, 1536, newRC4}, -} - -// defaultKeyExchangeOrder specifies a default set of key exchange algorithms -// with preferences. -var defaultKeyExchangeOrder = []string{ - // P384 and P521 are not constant-time yet, but since we don't - // reuse ephemeral keys, using them for ECDH should be OK. - kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, - kexAlgoDH14SHA1, kexAlgoDH1SHA1, -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher_test.go deleted file mode 100644 index ea27bd8a..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/cipher_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "testing" -) - -// TestCipherReversal tests that each cipher factory produces ciphers that can -// encrypt and decrypt some data successfully. -func TestCipherReversal(t *testing.T) { - testData := []byte("abcdefghijklmnopqrstuvwxyz012345") - testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345") - testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa") - - cryptBuffer := make([]byte, 32) - - for name, cipherMode := range cipherModes { - encrypter, err := cipherMode.createCipher(testKey, testIv) - if err != nil { - t.Errorf("failed to create encrypter for %q: %s", name, err) - continue - } - decrypter, err := cipherMode.createCipher(testKey, testIv) - if err != nil { - t.Errorf("failed to create decrypter for %q: %s", name, err) - continue - } - - copy(cryptBuffer, testData) - - encrypter.XORKeyStream(cryptBuffer, cryptBuffer) - if name == "none" { - if !bytes.Equal(cryptBuffer, testData) { - t.Errorf("encryption made change with 'none' cipher") - continue - } - } else { - if bytes.Equal(cryptBuffer, testData) { - t.Errorf("encryption made no change with %q", name) - continue - } - } - - decrypter.XORKeyStream(cryptBuffer, cryptBuffer) - if !bytes.Equal(cryptBuffer, testData) { - t.Errorf("decrypted bytes not equal to input with %q", name) - continue - } - } -} - -func TestDefaultCiphersExist(t *testing.T) { - for _, cipherAlgo := range DefaultCipherOrder { - if _, ok := cipherModes[cipherAlgo]; !ok { - t.Errorf("default cipher %q is unknown", cipherAlgo) - } - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client.go deleted file mode 100644 index e2d25570..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client.go +++ /dev/null @@ -1,524 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "sync" -) - -// ClientConn represents the client side of an SSH connection. -type ClientConn struct { - transport *transport - config *ClientConfig - chanList // channels associated with this connection - forwardList // forwarded tcpip connections from the remote side - globalRequest - - // Address as passed to the Dial function. - dialAddress string - - serverVersion string -} - -type globalRequest struct { - sync.Mutex - response chan interface{} -} - -// Client returns a new SSH client connection using c as the underlying transport. -func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) { - return clientWithAddress(c, "", config) -} - -func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientConn, error) { - conn := &ClientConn{ - transport: newTransport(c, config.rand(), true /* is client */), - config: config, - globalRequest: globalRequest{response: make(chan interface{}, 1)}, - dialAddress: addr, - } - - if err := conn.handshake(); err != nil { - conn.transport.Close() - return nil, fmt.Errorf("handshake failed: %v", err) - } - go conn.mainLoop() - return conn, nil -} - -// Close closes the connection. -func (c *ClientConn) Close() error { return c.transport.Close() } - -// LocalAddr returns the local network address. -func (c *ClientConn) LocalAddr() net.Addr { return c.transport.LocalAddr() } - -// RemoteAddr returns the remote network address. -func (c *ClientConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() } - -// handshake performs the client side key exchange. See RFC 4253 Section 7. -func (c *ClientConn) handshake() error { - clientVersion := []byte(packageVersion) - if c.config.ClientVersion != "" { - clientVersion = []byte(c.config.ClientVersion) - } - - serverVersion, err := exchangeVersions(c.transport.Conn, clientVersion) - if err != nil { - return err - } - c.serverVersion = string(serverVersion) - - clientKexInit := kexInitMsg{ - KexAlgos: c.config.Crypto.kexes(), - ServerHostKeyAlgos: supportedHostKeyAlgos, - CiphersClientServer: c.config.Crypto.ciphers(), - CiphersServerClient: c.config.Crypto.ciphers(), - MACsClientServer: c.config.Crypto.macs(), - MACsServerClient: c.config.Crypto.macs(), - CompressionClientServer: supportedCompressions, - CompressionServerClient: supportedCompressions, - } - kexInitPacket := marshal(msgKexInit, clientKexInit) - if err := c.transport.writePacket(kexInitPacket); err != nil { - return err - } - packet, err := c.transport.readPacket() - if err != nil { - return err - } - - var serverKexInit kexInitMsg - if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil { - return err - } - - algs := findAgreedAlgorithms(&clientKexInit, &serverKexInit) - if algs == nil { - return errors.New("ssh: no common algorithms") - } - - if serverKexInit.FirstKexFollows && algs.kex != serverKexInit.KexAlgos[0] { - // The server sent a Kex message for the wrong algorithm, - // which we have to ignore. - if _, err := c.transport.readPacket(); err != nil { - return err - } - } - - kex, ok := kexAlgoMap[algs.kex] - if !ok { - return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex) - } - - magics := handshakeMagics{ - clientVersion: clientVersion, - serverVersion: serverVersion, - clientKexInit: kexInitPacket, - serverKexInit: packet, - } - result, err := kex.Client(c.transport, c.config.rand(), &magics) - if err != nil { - return err - } - - err = verifyHostKeySignature(algs.hostKey, result.HostKey, result.H, result.Signature) - if err != nil { - return err - } - - if checker := c.config.HostKeyChecker; checker != nil { - err = checker.Check(c.dialAddress, c.transport.RemoteAddr(), algs.hostKey, result.HostKey) - if err != nil { - return err - } - } - - c.transport.prepareKeyChange(algs, result) - - if err = c.transport.writePacket([]byte{msgNewKeys}); err != nil { - return err - } - if packet, err = c.transport.readPacket(); err != nil { - return err - } - if packet[0] != msgNewKeys { - return UnexpectedMessageError{msgNewKeys, packet[0]} - } - return c.authenticate() -} - -// Verify the host key obtained in the key exchange. -func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte, signature []byte) error { - hostKey, rest, ok := ParsePublicKey(hostKeyBytes) - if len(rest) > 0 || !ok { - return errors.New("ssh: could not parse hostkey") - } - - sig, rest, ok := parseSignatureBody(signature) - if len(rest) > 0 || !ok { - return errors.New("ssh: signature parse error") - } - if sig.Format != hostKeyAlgo { - return fmt.Errorf("ssh: unexpected signature type %q", sig.Format) - } - - if !hostKey.Verify(data, sig.Blob) { - return errors.New("ssh: host key signature error") - } - return nil -} - -// mainLoop reads incoming messages and routes channel messages -// to their respective ClientChans. -func (c *ClientConn) mainLoop() { - defer func() { - c.transport.Close() - c.chanList.closeAll() - c.forwardList.closeAll() - }() - - for { - packet, err := c.transport.readPacket() - if err != nil { - break - } - // TODO(dfc) A note on blocking channel use. - // The msg, data and dataExt channels of a clientChan can - // cause this loop to block indefinitely if the consumer does - // not service them. - switch packet[0] { - case msgChannelData: - if len(packet) < 9 { - // malformed data packet - return - } - remoteId := binary.BigEndian.Uint32(packet[1:5]) - length := binary.BigEndian.Uint32(packet[5:9]) - packet = packet[9:] - - if length != uint32(len(packet)) { - return - } - ch, ok := c.getChan(remoteId) - if !ok { - return - } - ch.stdout.write(packet) - case msgChannelExtendedData: - if len(packet) < 13 { - // malformed data packet - return - } - remoteId := binary.BigEndian.Uint32(packet[1:5]) - datatype := binary.BigEndian.Uint32(packet[5:9]) - length := binary.BigEndian.Uint32(packet[9:13]) - packet = packet[13:] - - if length != uint32(len(packet)) { - return - } - // RFC 4254 5.2 defines data_type_code 1 to be data destined - // for stderr on interactive sessions. Other data types are - // silently discarded. - if datatype == 1 { - ch, ok := c.getChan(remoteId) - if !ok { - return - } - ch.stderr.write(packet) - } - default: - decoded, err := decode(packet) - if err != nil { - if _, ok := err.(UnexpectedMessageError); ok { - fmt.Printf("mainLoop: unexpected message: %v\n", err) - continue - } - return - } - switch msg := decoded.(type) { - case *channelOpenMsg: - c.handleChanOpen(msg) - case *channelOpenConfirmMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.msg <- msg - case *channelOpenFailureMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.msg <- msg - case *channelCloseMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.Close() - close(ch.msg) - c.chanList.remove(msg.PeersId) - case *channelEOFMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.stdout.eof() - // RFC 4254 is mute on how EOF affects dataExt messages but - // it is logical to signal EOF at the same time. - ch.stderr.eof() - case *channelRequestSuccessMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.msg <- msg - case *channelRequestFailureMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.msg <- msg - case *channelRequestMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - ch.msg <- msg - case *windowAdjustMsg: - ch, ok := c.getChan(msg.PeersId) - if !ok { - return - } - if !ch.remoteWin.add(msg.AdditionalBytes) { - // invalid window update - return - } - case *globalRequestMsg: - // This handles keepalive messages and matches - // the behaviour of OpenSSH. - if msg.WantReply { - c.transport.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{})) - } - case *globalRequestSuccessMsg, *globalRequestFailureMsg: - c.globalRequest.response <- msg - case *disconnectMsg: - return - default: - fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg) - } - } - } -} - -// Handle channel open messages from the remote side. -func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) { - if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { - c.sendConnectionFailed(msg.PeersId) - } - - switch msg.ChanType { - case "forwarded-tcpip": - laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData) - if !ok { - // invalid request - c.sendConnectionFailed(msg.PeersId) - return - } - - l, ok := c.forwardList.lookup(*laddr) - if !ok { - // TODO: print on a more structured log. - fmt.Println("could not find forward list entry for", laddr) - // Section 7.2, implementations MUST reject spurious incoming - // connections. - c.sendConnectionFailed(msg.PeersId) - return - } - raddr, rest, ok := parseTCPAddr(rest) - if !ok { - // invalid request - c.sendConnectionFailed(msg.PeersId) - return - } - ch := c.newChan(c.transport) - ch.remoteId = msg.PeersId - ch.remoteWin.add(msg.PeersWindow) - ch.maxPacket = msg.MaxPacketSize - - m := channelOpenConfirmMsg{ - PeersId: ch.remoteId, - MyId: ch.localId, - MyWindow: channelWindowSize, - MaxPacketSize: channelMaxPacketSize, - } - - c.transport.writePacket(marshal(msgChannelOpenConfirm, m)) - l <- forward{ch, raddr} - default: - // unknown channel type - m := channelOpenFailureMsg{ - PeersId: msg.PeersId, - Reason: UnknownChannelType, - Message: fmt.Sprintf("unknown channel type: %v", msg.ChanType), - Language: "en_US.UTF-8", - } - c.transport.writePacket(marshal(msgChannelOpenFailure, m)) - } -} - -// sendGlobalRequest sends a global request message as specified -// in RFC4254 section 4. To correctly synchronise messages, a lock -// is held internally until a response is returned. -func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) { - c.globalRequest.Lock() - defer c.globalRequest.Unlock() - if err := c.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil { - return nil, err - } - r := <-c.globalRequest.response - if r, ok := r.(*globalRequestSuccessMsg); ok { - return r, nil - } - return nil, errors.New("request failed") -} - -// sendConnectionFailed rejects an incoming channel identified -// by remoteId. -func (c *ClientConn) sendConnectionFailed(remoteId uint32) error { - m := channelOpenFailureMsg{ - PeersId: remoteId, - Reason: ConnectionFailed, - Message: "invalid request", - Language: "en_US.UTF-8", - } - return c.transport.writePacket(marshal(msgChannelOpenFailure, m)) -} - -// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr. -// RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardlist -// requires a valid *net.TCPAddr to operate, so we enforce that restriction here. -func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) { - addr, b, ok := parseString(b) - if !ok { - return nil, b, false - } - port, b, ok := parseUint32(b) - if !ok { - return nil, b, false - } - ip := net.ParseIP(string(addr)) - if ip == nil { - return nil, b, false - } - return &net.TCPAddr{IP: ip, Port: int(port)}, b, true -} - -// Dial connects to the given network address using net.Dial and -// then initiates a SSH handshake, returning the resulting client connection. -func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) { - conn, err := net.Dial(network, addr) - if err != nil { - return nil, err - } - return clientWithAddress(conn, addr, config) -} - -// A ClientConfig structure is used to configure a ClientConn. After one has -// been passed to an SSH function it must not be modified. -type ClientConfig struct { - // Rand provides the source of entropy for key exchange. If Rand is - // nil, the cryptographic random reader in package crypto/rand will - // be used. - Rand io.Reader - - // The username to authenticate. - User string - - // A slice of ClientAuth methods. Only the first instance - // of a particular RFC 4252 method will be used during authentication. - Auth []ClientAuth - - // HostKeyChecker, if not nil, is called during the cryptographic - // handshake to validate the server's host key. A nil HostKeyChecker - // implies that all host keys are accepted. - HostKeyChecker HostKeyChecker - - // Cryptographic-related configuration. - Crypto CryptoConfig - - // The identification string that will be used for the connection. - // If empty, a reasonable default is used. - ClientVersion string -} - -func (c *ClientConfig) rand() io.Reader { - if c.Rand == nil { - return rand.Reader - } - return c.Rand -} - -// Thread safe channel list. -type chanList struct { - // protects concurrent access to chans - sync.Mutex - // chans are indexed by the local id of the channel, clientChan.localId. - // The PeersId value of messages received by ClientConn.mainLoop is - // used to locate the right local clientChan in this slice. - chans []*clientChan -} - -// Allocate a new ClientChan with the next avail local id. -func (c *chanList) newChan(p packetConn) *clientChan { - c.Lock() - defer c.Unlock() - for i := range c.chans { - if c.chans[i] == nil { - ch := newClientChan(p, uint32(i)) - c.chans[i] = ch - return ch - } - } - i := len(c.chans) - ch := newClientChan(p, uint32(i)) - c.chans = append(c.chans, ch) - return ch -} - -func (c *chanList) getChan(id uint32) (*clientChan, bool) { - c.Lock() - defer c.Unlock() - if id >= uint32(len(c.chans)) { - return nil, false - } - return c.chans[id], true -} - -func (c *chanList) remove(id uint32) { - c.Lock() - defer c.Unlock() - c.chans[id] = nil -} - -func (c *chanList) closeAll() { - c.Lock() - defer c.Unlock() - - for _, ch := range c.chans { - if ch == nil { - continue - } - ch.Close() - close(ch.msg) - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth.go deleted file mode 100644 index 29be0ca4..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth.go +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "errors" - "fmt" - "io" - "net" -) - -// authenticate authenticates with the remote server. See RFC 4252. -func (c *ClientConn) authenticate() error { - // initiate user auth session - if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil { - return err - } - packet, err := c.transport.readPacket() - if err != nil { - return err - } - var serviceAccept serviceAcceptMsg - if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil { - return err - } - // during the authentication phase the client first attempts the "none" method - // then any untried methods suggested by the server. - tried, remain := make(map[string]bool), make(map[string]bool) - for auth := ClientAuth(new(noneAuth)); auth != nil; { - ok, methods, err := auth.auth(c.transport.sessionID, c.config.User, c.transport, c.config.rand()) - if err != nil { - return err - } - if ok { - // success - return nil - } - tried[auth.method()] = true - delete(remain, auth.method()) - for _, meth := range methods { - if tried[meth] { - // if we've tried meth already, skip it. - continue - } - remain[meth] = true - } - auth = nil - for _, a := range c.config.Auth { - if remain[a.method()] { - auth = a - break - } - } - } - return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried)) -} - -func keys(m map[string]bool) (s []string) { - for k := range m { - s = append(s, k) - } - return -} - -// HostKeyChecker represents a database of known server host keys. -type HostKeyChecker interface { - // Check is called during the handshake to check server's - // public key for unexpected changes. The hostKey argument is - // in SSH wire format. It can be parsed using - // ssh.ParsePublicKey. The address before DNS resolution is - // passed in the addr argument, so the key can also be checked - // against the hostname. - Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error -} - -// A ClientAuth represents an instance of an RFC 4252 authentication method. -type ClientAuth interface { - // auth authenticates user over transport t. - // Returns true if authentication is successful. - // If authentication is not successful, a []string of alternative - // method names is returned. - auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error) - - // method returns the RFC 4252 method name. - method() string -} - -// "none" authentication, RFC 4252 section 5.2. -type noneAuth int - -func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{ - User: user, - Service: serviceSSH, - Method: "none", - })); err != nil { - return false, nil, err - } - - return handleAuthResponse(c) -} - -func (n *noneAuth) method() string { - return "none" -} - -// "password" authentication, RFC 4252 Section 8. -type passwordAuth struct { - ClientPassword -} - -func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - type passwordAuthMsg struct { - User string - Service string - Method string - Reply bool - Password string - } - - pw, err := p.Password(user) - if err != nil { - return false, nil, err - } - - if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{ - User: user, - Service: serviceSSH, - Method: "password", - Reply: false, - Password: pw, - })); err != nil { - return false, nil, err - } - - return handleAuthResponse(c) -} - -func (p *passwordAuth) method() string { - return "password" -} - -// A ClientPassword implements access to a client's passwords. -type ClientPassword interface { - // Password returns the password to use for user. - Password(user string) (password string, err error) -} - -// ClientAuthPassword returns a ClientAuth using password authentication. -func ClientAuthPassword(impl ClientPassword) ClientAuth { - return &passwordAuth{impl} -} - -// ClientKeyring implements access to a client key ring. -type ClientKeyring interface { - // Key returns the i'th Publickey, or nil if no key exists at i. - Key(i int) (key PublicKey, err error) - - // Sign returns a signature of the given data using the i'th key - // and the supplied random source. - Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) -} - -// "publickey" authentication, RFC 4252 Section 7. -type publickeyAuth struct { - ClientKeyring -} - -type publickeyAuthMsg struct { - User string - Service string - Method string - // HasSig indicates to the receiver packet that the auth request is signed and - // should be used for authentication of the request. - HasSig bool - Algoname string - Pubkey string - // Sig is defined as []byte so marshal will exclude it during validateKey - Sig []byte `ssh:"rest"` -} - -func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - // Authentication is performed in two stages. The first stage sends an - // enquiry to test if each key is acceptable to the remote. The second - // stage attempts to authenticate with the valid keys obtained in the - // first stage. - - var index int - // a map of public keys to their index in the keyring - validKeys := make(map[int]PublicKey) - for { - key, err := p.Key(index) - if err != nil { - return false, nil, err - } - if key == nil { - // no more keys in the keyring - break - } - - if ok, err := p.validateKey(key, user, c); ok { - validKeys[index] = key - } else { - if err != nil { - return false, nil, err - } - } - index++ - } - - // methods that may continue if this auth is not successful. - var methods []string - for i, key := range validKeys { - pubkey := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() - data := buildDataSignedForAuth(session, userAuthRequestMsg{ - User: user, - Service: serviceSSH, - Method: p.method(), - }, []byte(algoname), pubkey) - sigBlob, err := p.Sign(i, rand, data) - if err != nil { - return false, nil, err - } - // manually wrap the serialized signature in a string - s := serializeSignature(key.PublicKeyAlgo(), sigBlob) - sig := make([]byte, stringLength(len(s))) - marshalString(sig, s) - msg := publickeyAuthMsg{ - User: user, - Service: serviceSSH, - Method: p.method(), - HasSig: true, - Algoname: algoname, - Pubkey: string(pubkey), - Sig: sig, - } - p := marshal(msgUserAuthRequest, msg) - if err := c.writePacket(p); err != nil { - return false, nil, err - } - success, methods, err := handleAuthResponse(c) - if err != nil { - return false, nil, err - } - if success { - return success, methods, err - } - } - return false, methods, nil -} - -// validateKey validates the key provided it is acceptable to the server. -func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (bool, error) { - pubkey := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() - msg := publickeyAuthMsg{ - User: user, - Service: serviceSSH, - Method: p.method(), - HasSig: false, - Algoname: algoname, - Pubkey: string(pubkey), - } - if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil { - return false, err - } - - return p.confirmKeyAck(key, c) -} - -func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) { - pubkey := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() - - for { - packet, err := c.readPacket() - if err != nil { - return false, err - } - switch packet[0] { - case msgUserAuthBanner: - // TODO(gpaul): add callback to present the banner to the user - case msgUserAuthPubKeyOk: - msg := userAuthPubKeyOkMsg{} - if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil { - return false, err - } - if msg.Algo != algoname || msg.PubKey != string(pubkey) { - return false, nil - } - return true, nil - case msgUserAuthFailure: - return false, nil - default: - return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]} - } - } - panic("unreachable") -} - -func (p *publickeyAuth) method() string { - return "publickey" -} - -// ClientAuthKeyring returns a ClientAuth using public key authentication. -func ClientAuthKeyring(impl ClientKeyring) ClientAuth { - return &publickeyAuth{impl} -} - -// handleAuthResponse returns whether the preceding authentication request succeeded -// along with a list of remaining authentication methods to try next and -// an error if an unexpected response was received. -func handleAuthResponse(c packetConn) (bool, []string, error) { - for { - packet, err := c.readPacket() - if err != nil { - return false, nil, err - } - - switch packet[0] { - case msgUserAuthBanner: - // TODO: add callback to present the banner to the user - case msgUserAuthFailure: - msg := userAuthFailureMsg{} - if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil { - return false, nil, err - } - return false, msg.Methods, nil - case msgUserAuthSuccess: - return true, nil, nil - case msgDisconnect: - return false, nil, io.EOF - default: - return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]} - } - } - panic("unreachable") -} - -// ClientAuthAgent returns a ClientAuth using public key authentication via -// an agent. -func ClientAuthAgent(agent *AgentClient) ClientAuth { - return ClientAuthKeyring(&agentKeyring{agent: agent}) -} - -// agentKeyring implements ClientKeyring. -type agentKeyring struct { - agent *AgentClient - keys []*AgentKey -} - -func (kr *agentKeyring) Key(i int) (key PublicKey, err error) { - if kr.keys == nil { - if kr.keys, err = kr.agent.RequestIdentities(); err != nil { - return - } - } - if i >= len(kr.keys) { - return - } - return kr.keys[i].Key() -} - -func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { - var key PublicKey - if key, err = kr.Key(i); err != nil { - return - } - if key == nil { - return nil, errors.New("ssh: key index out of range") - } - if sig, err = kr.agent.SignRequest(key, data); err != nil { - return - } - - // Unmarshal the signature. - - var ok bool - if _, sig, ok = parseString(sig); !ok { - return nil, errors.New("ssh: malformed signature response from agent") - } - if sig, _, ok = parseString(sig); !ok { - return nil, errors.New("ssh: malformed signature response from agent") - } - return sig, nil -} - -// ClientKeyboardInteractive should prompt the user for the given -// questions. -type ClientKeyboardInteractive interface { - // Challenge should print the questions, optionally disabling - // echoing (eg. for passwords), and return all the answers. - // Challenge may be called multiple times in a single - // session. After successful authentication, the server may - // send a challenge with no questions, for which the user and - // instruction messages should be printed. RFC 4256 section - // 3.3 details how the UI should behave for both CLI and - // GUI environments. - Challenge(user, instruction string, questions []string, echos []bool) ([]string, error) -} - -// ClientAuthKeyboardInteractive returns a ClientAuth using a -// prompt/response sequence controlled by the server. -func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth { - return &keyboardInteractiveAuth{impl} -} - -type keyboardInteractiveAuth struct { - ClientKeyboardInteractive -} - -func (k *keyboardInteractiveAuth) method() string { - return "keyboard-interactive" -} - -func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - type initiateMsg struct { - User string - Service string - Method string - Language string - Submethods string - } - - if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{ - User: user, - Service: serviceSSH, - Method: "keyboard-interactive", - })); err != nil { - return false, nil, err - } - - for { - packet, err := c.readPacket() - if err != nil { - return false, nil, err - } - - // like handleAuthResponse, but with less options. - switch packet[0] { - case msgUserAuthBanner: - // TODO: Print banners during userauth. - continue - case msgUserAuthInfoRequest: - // OK - case msgUserAuthFailure: - var msg userAuthFailureMsg - if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil { - return false, nil, err - } - return false, msg.Methods, nil - case msgUserAuthSuccess: - return true, nil, nil - default: - return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]} - } - - var msg userAuthInfoRequestMsg - if err := unmarshal(&msg, packet, packet[0]); err != nil { - return false, nil, err - } - - // Manually unpack the prompt/echo pairs. - rest := msg.Prompts - var prompts []string - var echos []bool - for i := 0; i < int(msg.NumPrompts); i++ { - prompt, r, ok := parseString(rest) - if !ok || len(r) == 0 { - return false, nil, errors.New("ssh: prompt format error") - } - prompts = append(prompts, string(prompt)) - echos = append(echos, r[0] != 0) - rest = r[1:] - } - - if len(rest) != 0 { - return false, nil, fmt.Errorf("ssh: junk following message %q", rest) - } - - answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos) - if err != nil { - return false, nil, err - } - - if len(answers) != len(prompts) { - return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") - } - responseLength := 1 + 4 - for _, a := range answers { - responseLength += stringLength(len(a)) - } - serialized := make([]byte, responseLength) - p := serialized - p[0] = msgUserAuthInfoResponse - p = p[1:] - p = marshalUint32(p, uint32(len(answers))) - for _, a := range answers { - p = marshalString(p, []byte(a)) - } - - if err := c.writePacket(serialized); err != nil { - return false, nil, err - } - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth_test.go deleted file mode 100644 index f2fc9c64..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_auth_test.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "crypto/dsa" - "io" - "io/ioutil" - "math/big" - "strings" - "testing" - - _ "crypto/sha1" -) - -// private key for mock server -const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU -70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx -9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF -tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z -s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc -qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT -+IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea -riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH -D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh -atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT -b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN -ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M -MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4 -KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8 -e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1 -D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+ -3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj -orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw -64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc -XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc -QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g -/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ -I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk -gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl -NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw== ------END RSA PRIVATE KEY-----` - -const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY----- -MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld -r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ -tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC -nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW -2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB -y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr -rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg== ------END RSA PRIVATE KEY-----` - -// keychain implements the ClientKeyring interface -type keychain struct { - keys []Signer -} - -func (k *keychain) Key(i int) (PublicKey, error) { - if i < 0 || i >= len(k.keys) { - return nil, nil - } - - return k.keys[i].PublicKey(), nil -} - -func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { - return k.keys[i].Sign(rand, data) -} - -func (k *keychain) add(key Signer) { - k.keys = append(k.keys, key) -} - -func (k *keychain) loadPEM(file string) error { - buf, err := ioutil.ReadFile(file) - if err != nil { - return err - } - key, err := ParsePrivateKey(buf) - if err != nil { - return err - } - k.add(key) - return nil -} - -// password implements the ClientPassword interface -type password string - -func (p password) Password(user string) (string, error) { - return string(p), nil -} - -type keyboardInteractive map[string]string - -func (cr *keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) { - var answers []string - for _, q := range questions { - answers = append(answers, (*cr)[q]) - } - return answers, nil -} - -// reused internally by tests -var ( - rsaKey Signer - dsaKey Signer - clientKeychain = new(keychain) - clientPassword = password("tiger") - serverConfig = &ServerConfig{ - PasswordCallback: func(conn *ServerConn, user, pass string) bool { - return user == "testuser" && pass == string(clientPassword) - }, - PublicKeyCallback: func(conn *ServerConn, user, algo string, pubkey []byte) bool { - key, _ := clientKeychain.Key(0) - expected := MarshalPublicKey(key) - algoname := key.PublicKeyAlgo() - return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected) - }, - KeyboardInteractiveCallback: func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool { - ans, err := client.Challenge("user", - "instruction", - []string{"question1", "question2"}, - []bool{true, true}) - if err != nil { - return false - } - ok := user == "testuser" && ans[0] == "answer1" && ans[1] == "answer2" - client.Challenge("user", "motd", nil, nil) - return ok - }, - } -) - -func init() { - var err error - rsaKey, err = ParsePrivateKey([]byte(testServerPrivateKey)) - if err != nil { - panic("unable to set private key: " + err.Error()) - } - rawDSAKey := new(dsa.PrivateKey) - - // taken from crypto/dsa/dsa_test.go - rawDSAKey.P, _ = new(big.Int).SetString("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF", 16) - rawDSAKey.Q, _ = new(big.Int).SetString("E1D3391245933D68A0714ED34BBCB7A1F422B9C1", 16) - rawDSAKey.G, _ = new(big.Int).SetString("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA", 16) - rawDSAKey.Y, _ = new(big.Int).SetString("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2", 16) - rawDSAKey.X, _ = new(big.Int).SetString("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A", 16) - - dsaKey, err = NewSignerFromKey(rawDSAKey) - if err != nil { - panic("NewSignerFromKey: " + err.Error()) - } - clientKeychain.add(rsaKey) - serverConfig.AddHostKey(rsaKey) -} - -// newMockAuthServer creates a new Server bound to -// the loopback interface. The server exits after -// processing one handshake. -func newMockAuthServer(t *testing.T) string { - l, err := Listen("tcp", "127.0.0.1:0", serverConfig) - if err != nil { - t.Fatalf("unable to newMockAuthServer: %s", err) - } - go func() { - defer l.Close() - c, err := l.Accept() - if err != nil { - t.Errorf("Unable to accept incoming connection: %v", err) - return - } - if err := c.Handshake(); err != nil { - // not Errorf because this is expected to - // fail for some tests. - t.Logf("Handshaking error: %v", err) - return - } - defer c.Close() - }() - return l.Addr().String() -} - -func TestClientAuthPublicKey(t *testing.T) { - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(clientKeychain), - }, - } - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("unable to dial remote side: %s", err) - } - c.Close() -} - -func TestClientAuthPassword(t *testing.T) { - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthPassword(clientPassword), - }, - } - - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("unable to dial remote side: %s", err) - } - c.Close() -} - -func TestClientAuthWrongPassword(t *testing.T) { - wrongPw := password("wrong") - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthPassword(wrongPw), - ClientAuthKeyring(clientKeychain), - }, - } - - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("unable to dial remote side: %s", err) - } - c.Close() -} - -func TestClientAuthKeyboardInteractive(t *testing.T) { - answers := keyboardInteractive(map[string]string{ - "question1": "answer1", - "question2": "answer2", - }) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyboardInteractive(&answers), - }, - } - - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("unable to dial remote side: %s", err) - } - c.Close() -} - -func TestClientAuthWrongKeyboardInteractive(t *testing.T) { - answers := keyboardInteractive(map[string]string{ - "question1": "answer1", - "question2": "WRONG", - }) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyboardInteractive(&answers), - }, - } - - c, err := Dial("tcp", newMockAuthServer(t), config) - if err == nil { - c.Close() - t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive") - } -} - -// the mock server will only authenticate ssh-rsa keys -func TestClientAuthInvalidPublicKey(t *testing.T) { - kc := new(keychain) - - kc.add(dsaKey) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(kc), - }, - } - - c, err := Dial("tcp", newMockAuthServer(t), config) - if err == nil { - c.Close() - t.Fatalf("dsa private key should not have authenticated with rsa public key") - } -} - -// the client should authenticate with the second key -func TestClientAuthRSAandDSA(t *testing.T) { - kc := new(keychain) - kc.add(dsaKey) - kc.add(rsaKey) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(kc), - }, - } - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("client could not authenticate with rsa key: %v", err) - } - c.Close() -} - -func TestClientHMAC(t *testing.T) { - kc := new(keychain) - kc.add(rsaKey) - for _, mac := range DefaultMACOrder { - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(kc), - }, - Crypto: CryptoConfig{ - MACs: []string{mac}, - }, - } - c, err := Dial("tcp", newMockAuthServer(t), config) - if err != nil { - t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err) - } - c.Close() - } -} - -// issue 4285. -func TestClientUnsupportedCipher(t *testing.T) { - kc := new(keychain) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(kc), - }, - Crypto: CryptoConfig{ - Ciphers: []string{"aes128-cbc"}, // not currently supported - }, - } - c, err := Dial("tcp", newMockAuthServer(t), config) - if err == nil { - t.Errorf("expected no ciphers in common") - c.Close() - } -} - -func TestClientUnsupportedKex(t *testing.T) { - kc := new(keychain) - config := &ClientConfig{ - User: "testuser", - Auth: []ClientAuth{ - ClientAuthKeyring(kc), - }, - Crypto: CryptoConfig{ - KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported - }, - } - c, err := Dial("tcp", newMockAuthServer(t), config) - if err == nil || !strings.Contains(err.Error(), "no common algorithms") { - t.Errorf("got %v, expected 'no common algorithms'", err) - } - if c != nil { - c.Close() - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common.go deleted file mode 100644 index 4870e569..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "crypto" - "fmt" - "sync" - - _ "crypto/sha1" - _ "crypto/sha256" - _ "crypto/sha512" -) - -// These are string constants in the SSH protocol. -const ( - compressionNone = "none" - serviceUserAuth = "ssh-userauth" - serviceSSH = "ssh-connection" -) - -var supportedKexAlgos = []string{ - kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, - kexAlgoDH14SHA1, kexAlgoDH1SHA1, -} - -var supportedHostKeyAlgos = []string{ - KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, - KeyAlgoRSA, KeyAlgoDSA, -} - -var supportedCompressions = []string{compressionNone} - -// hashFuncs keeps the mapping of supported algorithms to their respective -// hashes needed for signature verification. -var hashFuncs = map[string]crypto.Hash{ - KeyAlgoRSA: crypto.SHA1, - KeyAlgoDSA: crypto.SHA1, - KeyAlgoECDSA256: crypto.SHA256, - KeyAlgoECDSA384: crypto.SHA384, - KeyAlgoECDSA521: crypto.SHA512, - CertAlgoRSAv01: crypto.SHA1, - CertAlgoDSAv01: crypto.SHA1, - CertAlgoECDSA256v01: crypto.SHA256, - CertAlgoECDSA384v01: crypto.SHA384, - CertAlgoECDSA521v01: crypto.SHA512, -} - -// UnexpectedMessageError results when the SSH message that we received didn't -// match what we wanted. -type UnexpectedMessageError struct { - expected, got uint8 -} - -func (u UnexpectedMessageError) Error() string { - return fmt.Sprintf("ssh: unexpected message type %d (expected %d)", u.got, u.expected) -} - -// ParseError results from a malformed SSH message. -type ParseError struct { - msgType uint8 -} - -func (p ParseError) Error() string { - return fmt.Sprintf("ssh: parse error in message type %d", p.msgType) -} - -func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) { - for _, clientAlgo := range clientAlgos { - for _, serverAlgo := range serverAlgos { - if clientAlgo == serverAlgo { - return clientAlgo, true - } - } - } - return -} - -func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) { - for _, clientCipher := range clientCiphers { - for _, serverCipher := range serverCiphers { - // reject the cipher if we have no cipherModes definition - if clientCipher == serverCipher && cipherModes[clientCipher] != nil { - return clientCipher, true - } - } - } - return -} - -type algorithms struct { - kex string - hostKey string - wCipher string - rCipher string - rMAC string - wMAC string - rCompression string - wCompression string -} - -func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) { - var ok bool - result := &algorithms{} - result.kex, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos) - if !ok { - return - } - - result.hostKey, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) - if !ok { - return - } - - result.wCipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) - if !ok { - return - } - - result.rCipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) - if !ok { - return - } - - result.wMAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) - if !ok { - return - } - - result.rMAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) - if !ok { - return - } - - result.wCompression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) - if !ok { - return - } - - result.rCompression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) - if !ok { - return - } - - return result -} - -// Cryptographic configuration common to both ServerConfig and ClientConfig. -type CryptoConfig struct { - // The allowed key exchanges algorithms. If unspecified then a - // default set of algorithms is used. - KeyExchanges []string - - // The allowed cipher algorithms. If unspecified then DefaultCipherOrder is - // used. - Ciphers []string - - // The allowed MAC algorithms. If unspecified then DefaultMACOrder is used. - MACs []string -} - -func (c *CryptoConfig) ciphers() []string { - if c.Ciphers == nil { - return DefaultCipherOrder - } - return c.Ciphers -} - -func (c *CryptoConfig) kexes() []string { - if c.KeyExchanges == nil { - return defaultKeyExchangeOrder - } - return c.KeyExchanges -} - -func (c *CryptoConfig) macs() []string { - if c.MACs == nil { - return DefaultMACOrder - } - return c.MACs -} - -// serialize a signed slice according to RFC 4254 6.6. The name should -// be a key type name, rather than a cert type name. -func serializeSignature(name string, sig []byte) []byte { - length := stringLength(len(name)) - length += stringLength(len(sig)) - - ret := make([]byte, length) - r := marshalString(ret, []byte(name)) - r = marshalString(r, sig) - - return ret -} - -// MarshalPublicKey serializes a supported key or certificate for use -// by the SSH wire protocol. It can be used for comparison with the -// pubkey argument of ServerConfig's PublicKeyCallback as well as for -// generating an authorized_keys or host_keys file. -func MarshalPublicKey(key PublicKey) []byte { - // See also RFC 4253 6.6. - algoname := key.PublicKeyAlgo() - blob := key.Marshal() - - length := stringLength(len(algoname)) - length += len(blob) - ret := make([]byte, length) - r := marshalString(ret, []byte(algoname)) - copy(r, blob) - return ret -} - -// pubAlgoToPrivAlgo returns the private key algorithm format name that -// corresponds to a given public key algorithm format name. For most -// public keys, the private key algorithm name is the same. For some -// situations, such as openssh certificates, the private key algorithm and -// public key algorithm names differ. This accounts for those situations. -func pubAlgoToPrivAlgo(pubAlgo string) string { - switch pubAlgo { - case CertAlgoRSAv01: - return KeyAlgoRSA - case CertAlgoDSAv01: - return KeyAlgoDSA - case CertAlgoECDSA256v01: - return KeyAlgoECDSA256 - case CertAlgoECDSA384v01: - return KeyAlgoECDSA384 - case CertAlgoECDSA521v01: - return KeyAlgoECDSA521 - } - return pubAlgo -} - -// buildDataSignedForAuth returns the data that is signed in order to prove -// possession of a private key. See RFC 4252, section 7. -func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte { - user := []byte(req.User) - service := []byte(req.Service) - method := []byte(req.Method) - - length := stringLength(len(sessionId)) - length += 1 - length += stringLength(len(user)) - length += stringLength(len(service)) - length += stringLength(len(method)) - length += 1 - length += stringLength(len(algo)) - length += stringLength(len(pubKey)) - - ret := make([]byte, length) - r := marshalString(ret, sessionId) - r[0] = msgUserAuthRequest - r = r[1:] - r = marshalString(r, user) - r = marshalString(r, service) - r = marshalString(r, method) - r[0] = 1 - r = r[1:] - r = marshalString(r, algo) - r = marshalString(r, pubKey) - return ret -} - -// safeString sanitises s according to RFC 4251, section 9.2. -// All control characters except tab, carriage return and newline are -// replaced by 0x20. -func safeString(s string) string { - out := []byte(s) - for i, c := range out { - if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 { - out[i] = 0x20 - } - } - return string(out) -} - -func appendU16(buf []byte, n uint16) []byte { - return append(buf, byte(n>>8), byte(n)) -} - -func appendU32(buf []byte, n uint32) []byte { - return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) -} - -func appendInt(buf []byte, n int) []byte { - return appendU32(buf, uint32(n)) -} - -func appendString(buf []byte, s string) []byte { - buf = appendU32(buf, uint32(len(s))) - buf = append(buf, s...) - return buf -} - -func appendBool(buf []byte, b bool) []byte { - if b { - buf = append(buf, 1) - } else { - buf = append(buf, 0) - } - return buf -} - -// newCond is a helper to hide the fact that there is no usable zero -// value for sync.Cond. -func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) } - -// window represents the buffer available to clients -// wishing to write to a channel. -type window struct { - *sync.Cond - win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 -} - -// add adds win to the amount of window available -// for consumers. -func (w *window) add(win uint32) bool { - // a zero sized window adjust is a noop. - if win == 0 { - return true - } - w.L.Lock() - if w.win+win < win { - w.L.Unlock() - return false - } - w.win += win - // It is unusual that multiple goroutines would be attempting to reserve - // window space, but not guaranteed. Use broadcast to notify all waiters - // that additional window is available. - w.Broadcast() - w.L.Unlock() - return true -} - -// reserve reserves win from the available window capacity. -// If no capacity remains, reserve will block. reserve may -// return less than requested. -func (w *window) reserve(win uint32) uint32 { - w.L.Lock() - for w.win == 0 { - w.Wait() - } - if w.win < win { - win = w.win - } - w.win -= win - w.L.Unlock() - return win -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common_test.go deleted file mode 100644 index d9df56fa..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/common_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "io" - "net" - "testing" -) - -func TestSafeString(t *testing.T) { - strings := map[string]string{ - "\x20\x0d\x0a": "\x20\x0d\x0a", - "flibble": "flibble", - "new\x20line": "new\x20line", - "123456\x07789": "123456 789", - "\t\t\x10\r\n": "\t\t \r\n", - } - - for s, expected := range strings { - actual := safeString(s) - if expected != actual { - t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual)) - } - } -} - -// Make sure Read/Write are not exposed. -func TestConnHideRWMethods(t *testing.T) { - for _, c := range []interface{}{new(ServerConn), new(ClientConn)} { - if _, ok := c.(io.Reader); ok { - t.Errorf("%T implements io.Reader", c) - } - if _, ok := c.(io.Writer); ok { - t.Errorf("%T implements io.Writer", c) - } - } -} - -func TestConnSupportsLocalRemoteMethods(t *testing.T) { - type LocalAddr interface { - LocalAddr() net.Addr - } - type RemoteAddr interface { - RemoteAddr() net.Addr - } - for _, c := range []interface{}{new(ServerConn), new(ClientConn)} { - if _, ok := c.(LocalAddr); !ok { - t.Errorf("%T does not implement LocalAddr", c) - } - if _, ok := c.(RemoteAddr); !ok { - t.Errorf("%T does not implement RemoteAddr", c) - } - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys_test.go deleted file mode 100644 index 3c4b7351..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package ssh - -import ( - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "reflect" - "strings" - "testing" -) - -var ( - ecdsaKey Signer - ecdsa384Key Signer - ecdsa521Key Signer - testCertKey Signer -) - -type testSigner struct { - Signer - pub PublicKey -} - -func (ts *testSigner) PublicKey() PublicKey { - if ts.pub != nil { - return ts.pub - } - return ts.Signer.PublicKey() -} - -func init() { - raw256, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - ecdsaKey, _ = NewSignerFromKey(raw256) - - raw384, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - ecdsa384Key, _ = NewSignerFromKey(raw384) - - raw521, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - ecdsa521Key, _ = NewSignerFromKey(raw521) - - // Create a cert and sign it for use in tests. - testCert := &OpenSSHCertV01{ - Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil - Key: ecdsaKey.PublicKey(), - ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage - ValidAfter: 0, // unix epoch - ValidBefore: maxUint64, // The end of currently representable time. - Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil - SignatureKey: rsaKey.PublicKey(), - } - sigBytes, _ := rsaKey.Sign(rand.Reader, testCert.BytesForSigning()) - testCert.Signature = &signature{ - Format: testCert.SignatureKey.PublicKeyAlgo(), - Blob: sigBytes, - } - testCertKey = &testSigner{ - Signer: ecdsaKey, - pub: testCert, - } -} - -func rawKey(pub PublicKey) interface{} { - switch k := pub.(type) { - case *rsaPublicKey: - return (*rsa.PublicKey)(k) - case *dsaPublicKey: - return (*dsa.PublicKey)(k) - case *ecdsaPublicKey: - return (*ecdsa.PublicKey)(k) - case *OpenSSHCertV01: - return k - } - panic("unknown key type") -} - -func TestKeyMarshalParse(t *testing.T) { - keys := []Signer{rsaKey, dsaKey, ecdsaKey, ecdsa384Key, ecdsa521Key, testCertKey} - for _, priv := range keys { - pub := priv.PublicKey() - roundtrip, rest, ok := ParsePublicKey(MarshalPublicKey(pub)) - if !ok { - t.Errorf("ParsePublicKey(%T) failed", pub) - } - - if len(rest) > 0 { - t.Errorf("ParsePublicKey(%T): trailing junk", pub) - } - - k1 := rawKey(pub) - k2 := rawKey(roundtrip) - - if !reflect.DeepEqual(k1, k2) { - t.Errorf("got %#v in roundtrip, want %#v", k2, k1) - } - } -} - -func TestUnsupportedCurves(t *testing.T) { - raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) - if err != nil { - t.Fatalf("GenerateKey: %v", err) - } - - if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P256") { - t.Fatalf("NewPrivateKey should not succeed with P224, got: %v", err) - } - - if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P256") { - t.Fatalf("NewPublicKey should not succeed with P224, got: %v", err) - } -} - -func TestNewPublicKey(t *testing.T) { - keys := []Signer{rsaKey, dsaKey, ecdsaKey} - for _, k := range keys { - raw := rawKey(k.PublicKey()) - pub, err := NewPublicKey(raw) - if err != nil { - t.Errorf("NewPublicKey(%#v): %v", raw, err) - } - if !reflect.DeepEqual(k.PublicKey(), pub) { - t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey()) - } - } -} - -func TestKeySignVerify(t *testing.T) { - keys := []Signer{rsaKey, dsaKey, ecdsaKey, testCertKey} - for _, priv := range keys { - pub := priv.PublicKey() - - data := []byte("sign me") - sig, err := priv.Sign(rand.Reader, data) - if err != nil { - t.Fatalf("Sign(%T): %v", priv, err) - } - - if !pub.Verify(data, sig) { - t.Errorf("publicKey.Verify(%T) failed", priv) - } - } -} - -func TestParseRSAPrivateKey(t *testing.T) { - key, err := ParsePrivateKey([]byte(testServerPrivateKey)) - if err != nil { - t.Fatalf("ParsePrivateKey: %v", err) - } - - rsa, ok := key.(*rsaPrivateKey) - if !ok { - t.Fatalf("got %T, want *rsa.PrivateKey", rsa) - } - - if err := rsa.Validate(); err != nil { - t.Errorf("Validate: %v", err) - } -} - -func TestParseECPrivateKey(t *testing.T) { - // Taken from the data in test/ . - pem := []byte(`-----BEGIN EC PRIVATE KEY----- -MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49 -AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ -6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA== ------END EC PRIVATE KEY-----`) - - key, err := ParsePrivateKey(pem) - if err != nil { - t.Fatalf("ParsePrivateKey: %v", err) - } - - ecKey, ok := key.(*ecdsaPrivateKey) - if !ok { - t.Fatalf("got %T, want *ecdsaPrivateKey", ecKey) - } - - if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) { - t.Fatalf("public key does not validate.") - } -} - -// ssh-keygen -t dsa -f /tmp/idsa.pem -var dsaPEM = `-----BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQD6PDSEyXiI9jfNs97WuM46MSDCYlOqWw80ajN16AohtBncs1YB -lHk//dQOvCYOsYaE+gNix2jtoRjwXhDsc25/IqQbU1ahb7mB8/rsaILRGIbA5WH3 -EgFtJmXFovDz3if6F6TzvhFpHgJRmLYVR8cqsezL3hEZOvvs2iH7MorkxwIVAJHD -nD82+lxh2fb4PMsIiaXudAsBAoGAQRf7Q/iaPRn43ZquUhd6WwvirqUj+tkIu6eV -2nZWYmXLlqFQKEy4Tejl7Wkyzr2OSYvbXLzo7TNxLKoWor6ips0phYPPMyXld14r -juhT24CrhOzuLMhDduMDi032wDIZG4Y+K7ElU8Oufn8Sj5Wge8r6ANmmVgmFfynr -FhdYCngCgYEA3ucGJ93/Mx4q4eKRDxcWD3QzWyqpbRVRRV1Vmih9Ha/qC994nJFz -DQIdjxDIT2Rk2AGzMqFEB68Zc3O+Wcsmz5eWWzEwFxaTwOGWTyDqsDRLm3fD+QYj -nOwuxb0Kce+gWI8voWcqC9cyRm09jGzu2Ab3Bhtpg8JJ8L7gS3MRZK4CFEx4UAfY -Fmsr0W6fHB9nhS4/UXM8 ------END DSA PRIVATE KEY-----` - -func TestParseDSA(t *testing.T) { - s, err := ParsePrivateKey([]byte(dsaPEM)) - if err != nil { - t.Fatalf("ParsePrivateKey returned error: %s", err) - } - - data := []byte("sign me") - sig, err := s.Sign(rand.Reader, data) - if err != nil { - t.Fatalf("dsa.Sign: %v", err) - } - - if !s.PublicKey().Verify(data, sig) { - t.Error("Verify failed.") - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server.go deleted file mode 100644 index b4defbef..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server.go +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "sync" - - _ "crypto/sha1" -) - -type ServerConfig struct { - hostKeys []Signer - - // Rand provides the source of entropy for key exchange. If Rand is - // nil, the cryptographic random reader in package crypto/rand will - // be used. - Rand io.Reader - - // NoClientAuth is true if clients are allowed to connect without - // authenticating. - NoClientAuth bool - - // PasswordCallback, if non-nil, is called when a user attempts to - // authenticate using a password. It may be called concurrently from - // several goroutines. - PasswordCallback func(conn *ServerConn, user, password string) bool - - // PublicKeyCallback, if non-nil, is called when a client attempts public - // key authentication. It must return true if the given public key is - // valid for the given user. - PublicKeyCallback func(conn *ServerConn, user, algo string, pubkey []byte) bool - - // KeyboardInteractiveCallback, if non-nil, is called when - // keyboard-interactive authentication is selected (RFC - // 4256). The client object's Challenge function should be - // used to query the user. The callback may offer multiple - // Challenge rounds. To avoid information leaks, the client - // should be presented a challenge even if the user is - // unknown. - KeyboardInteractiveCallback func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool - - // Cryptographic-related configuration. - Crypto CryptoConfig -} - -func (c *ServerConfig) rand() io.Reader { - if c.Rand == nil { - return rand.Reader - } - return c.Rand -} - -// AddHostKey adds a private key as a host key. If an existing host -// key exists with the same algorithm, it is overwritten. -func (s *ServerConfig) AddHostKey(key Signer) { - for i, k := range s.hostKeys { - if k.PublicKey().PublicKeyAlgo() == key.PublicKey().PublicKeyAlgo() { - s.hostKeys[i] = key - return - } - } - - s.hostKeys = append(s.hostKeys, key) -} - -// SetRSAPrivateKey sets the private key for a Server. A Server must have a -// private key configured in order to accept connections. The private key must -// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa" -// typically contains such a key. -func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error { - priv, err := ParsePrivateKey(pemBytes) - if err != nil { - return err - } - s.AddHostKey(priv) - return nil -} - -// cachedPubKey contains the results of querying whether a public key is -// acceptable for a user. The cache only applies to a single ServerConn. -type cachedPubKey struct { - user, algo string - pubKey []byte - result bool -} - -const maxCachedPubKeys = 16 - -// A ServerConn represents an incoming connection. -type ServerConn struct { - transport *transport - config *ServerConfig - - channels map[uint32]*serverChan - nextChanId uint32 - - // lock protects err and channels. - lock sync.Mutex - err error - - // cachedPubKeys contains the cache results of tests for public keys. - // Since SSH clients will query whether a public key is acceptable - // before attempting to authenticate with it, we end up with duplicate - // queries for public key validity. - cachedPubKeys []cachedPubKey - - // User holds the successfully authenticated user name. - // It is empty if no authentication is used. It is populated before - // any authentication callback is called and not assigned to after that. - User string - - // ClientVersion is the client's version, populated after - // Handshake is called. It should not be modified. - ClientVersion []byte - - // Our version. - serverVersion []byte -} - -// Server returns a new SSH server connection -// using c as the underlying transport. -func Server(c net.Conn, config *ServerConfig) *ServerConn { - return &ServerConn{ - transport: newTransport(c, config.rand(), false /* not client */), - channels: make(map[uint32]*serverChan), - config: config, - } -} - -// signAndMarshal signs the data with the appropriate algorithm, -// and serializes the result in SSH wire format. -func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { - sig, err := k.Sign(rand, data) - if err != nil { - return nil, err - } - - return serializeSignature(k.PublicKey().PrivateKeyAlgo(), sig), nil -} - -// Close closes the connection. -func (s *ServerConn) Close() error { return s.transport.Close() } - -// LocalAddr returns the local network address. -func (c *ServerConn) LocalAddr() net.Addr { return c.transport.LocalAddr() } - -// RemoteAddr returns the remote network address. -func (c *ServerConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() } - -// Handshake performs an SSH transport and client authentication on the given ServerConn. -func (s *ServerConn) Handshake() error { - var err error - s.serverVersion = []byte(packageVersion) - s.ClientVersion, err = exchangeVersions(s.transport.Conn, s.serverVersion) - if err != nil { - return err - } - if err := s.clientInitHandshake(nil, nil); err != nil { - return err - } - - var packet []byte - if packet, err = s.transport.readPacket(); err != nil { - return err - } - var serviceRequest serviceRequestMsg - if err := unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil { - return err - } - if serviceRequest.Service != serviceUserAuth { - return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") - } - serviceAccept := serviceAcceptMsg{ - Service: serviceUserAuth, - } - if err := s.transport.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil { - return err - } - - if err := s.authenticate(); err != nil { - return err - } - return err -} - -func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexInitPacket []byte) (err error) { - serverKexInit := kexInitMsg{ - KexAlgos: s.config.Crypto.kexes(), - CiphersClientServer: s.config.Crypto.ciphers(), - CiphersServerClient: s.config.Crypto.ciphers(), - MACsClientServer: s.config.Crypto.macs(), - MACsServerClient: s.config.Crypto.macs(), - CompressionClientServer: supportedCompressions, - CompressionServerClient: supportedCompressions, - } - for _, k := range s.config.hostKeys { - serverKexInit.ServerHostKeyAlgos = append( - serverKexInit.ServerHostKeyAlgos, k.PublicKey().PublicKeyAlgo()) - } - - serverKexInitPacket := marshal(msgKexInit, serverKexInit) - if err = s.transport.writePacket(serverKexInitPacket); err != nil { - return - } - - if clientKexInitPacket == nil { - clientKexInit = new(kexInitMsg) - if clientKexInitPacket, err = s.transport.readPacket(); err != nil { - return - } - if err = unmarshal(clientKexInit, clientKexInitPacket, msgKexInit); err != nil { - return - } - } - - algs := findAgreedAlgorithms(clientKexInit, &serverKexInit) - if algs == nil { - return errors.New("ssh: no common algorithms") - } - - if clientKexInit.FirstKexFollows && algs.kex != clientKexInit.KexAlgos[0] { - // The client sent a Kex message for the wrong algorithm, - // which we have to ignore. - if _, err = s.transport.readPacket(); err != nil { - return - } - } - - var hostKey Signer - for _, k := range s.config.hostKeys { - if algs.hostKey == k.PublicKey().PublicKeyAlgo() { - hostKey = k - } - } - - kex, ok := kexAlgoMap[algs.kex] - if !ok { - return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex) - } - - magics := handshakeMagics{ - serverVersion: s.serverVersion, - clientVersion: s.ClientVersion, - serverKexInit: marshal(msgKexInit, serverKexInit), - clientKexInit: clientKexInitPacket, - } - result, err := kex.Server(s.transport, s.config.rand(), &magics, hostKey) - if err != nil { - return err - } - - if err = s.transport.prepareKeyChange(algs, result); err != nil { - return err - } - - if err = s.transport.writePacket([]byte{msgNewKeys}); err != nil { - return - } - if packet, err := s.transport.readPacket(); err != nil { - return err - } else if packet[0] != msgNewKeys { - return UnexpectedMessageError{msgNewKeys, packet[0]} - } - - return -} - -func isAcceptableAlgo(algo string) bool { - switch algo { - case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, - CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: - return true - } - return false -} - -// testPubKey returns true if the given public key is acceptable for the user. -func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool { - if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) { - return false - } - - for _, c := range s.cachedPubKeys { - if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) { - return c.result - } - } - - result := s.config.PublicKeyCallback(s, user, algo, pubKey) - if len(s.cachedPubKeys) < maxCachedPubKeys { - c := cachedPubKey{ - user: user, - algo: algo, - pubKey: make([]byte, len(pubKey)), - result: result, - } - copy(c.pubKey, pubKey) - s.cachedPubKeys = append(s.cachedPubKeys, c) - } - - return result -} - -func (s *ServerConn) authenticate() error { - var userAuthReq userAuthRequestMsg - var err error - var packet []byte - -userAuthLoop: - for { - if packet, err = s.transport.readPacket(); err != nil { - return err - } - if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil { - return err - } - - if userAuthReq.Service != serviceSSH { - return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) - } - - switch userAuthReq.Method { - case "none": - if s.config.NoClientAuth { - break userAuthLoop - } - case "password": - if s.config.PasswordCallback == nil { - break - } - payload := userAuthReq.Payload - if len(payload) < 1 || payload[0] != 0 { - return ParseError{msgUserAuthRequest} - } - payload = payload[1:] - password, payload, ok := parseString(payload) - if !ok || len(payload) > 0 { - return ParseError{msgUserAuthRequest} - } - - s.User = userAuthReq.User - if s.config.PasswordCallback(s, userAuthReq.User, string(password)) { - break userAuthLoop - } - case "keyboard-interactive": - if s.config.KeyboardInteractiveCallback == nil { - break - } - - s.User = userAuthReq.User - if s.config.KeyboardInteractiveCallback(s, s.User, &sshClientKeyboardInteractive{s}) { - break userAuthLoop - } - case "publickey": - if s.config.PublicKeyCallback == nil { - break - } - payload := userAuthReq.Payload - if len(payload) < 1 { - return ParseError{msgUserAuthRequest} - } - isQuery := payload[0] == 0 - payload = payload[1:] - algoBytes, payload, ok := parseString(payload) - if !ok { - return ParseError{msgUserAuthRequest} - } - algo := string(algoBytes) - - pubKey, payload, ok := parseString(payload) - if !ok { - return ParseError{msgUserAuthRequest} - } - if isQuery { - // The client can query if the given public key - // would be okay. - if len(payload) > 0 { - return ParseError{msgUserAuthRequest} - } - if s.testPubKey(userAuthReq.User, algo, pubKey) { - okMsg := userAuthPubKeyOkMsg{ - Algo: algo, - PubKey: string(pubKey), - } - if err = s.transport.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil { - return err - } - continue userAuthLoop - } - } else { - sig, payload, ok := parseSignature(payload) - if !ok || len(payload) > 0 { - return ParseError{msgUserAuthRequest} - } - // Ensure the public key algo and signature algo - // are supported. Compare the private key - // algorithm name that corresponds to algo with - // sig.Format. This is usually the same, but - // for certs, the names differ. - if !isAcceptableAlgo(algo) || !isAcceptableAlgo(sig.Format) || pubAlgoToPrivAlgo(algo) != sig.Format { - break - } - signedData := buildDataSignedForAuth(s.transport.sessionID, userAuthReq, algoBytes, pubKey) - key, _, ok := ParsePublicKey(pubKey) - if !ok { - return ParseError{msgUserAuthRequest} - } - - if !key.Verify(signedData, sig.Blob) { - return ParseError{msgUserAuthRequest} - } - // TODO(jmpittman): Implement full validation for certificates. - s.User = userAuthReq.User - if s.testPubKey(userAuthReq.User, algo, pubKey) { - break userAuthLoop - } - } - } - - var failureMsg userAuthFailureMsg - if s.config.PasswordCallback != nil { - failureMsg.Methods = append(failureMsg.Methods, "password") - } - if s.config.PublicKeyCallback != nil { - failureMsg.Methods = append(failureMsg.Methods, "publickey") - } - if s.config.KeyboardInteractiveCallback != nil { - failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") - } - - if len(failureMsg.Methods) == 0 { - return errors.New("ssh: no authentication methods configured but NoClientAuth is also false") - } - - if err = s.transport.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil { - return err - } - } - - packet = []byte{msgUserAuthSuccess} - if err = s.transport.writePacket(packet); err != nil { - return err - } - - return nil -} - -// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by -// asking the client on the other side of a ServerConn. -type sshClientKeyboardInteractive struct { - *ServerConn -} - -func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { - if len(questions) != len(echos) { - return nil, errors.New("ssh: echos and questions must have equal length") - } - - var prompts []byte - for i := range questions { - prompts = appendString(prompts, questions[i]) - prompts = appendBool(prompts, echos[i]) - } - - if err := c.transport.writePacket(marshal(msgUserAuthInfoRequest, userAuthInfoRequestMsg{ - Instruction: instruction, - NumPrompts: uint32(len(questions)), - Prompts: prompts, - })); err != nil { - return nil, err - } - - packet, err := c.transport.readPacket() - if err != nil { - return nil, err - } - if packet[0] != msgUserAuthInfoResponse { - return nil, UnexpectedMessageError{msgUserAuthInfoResponse, packet[0]} - } - packet = packet[1:] - - n, packet, ok := parseUint32(packet) - if !ok || int(n) != len(questions) { - return nil, &ParseError{msgUserAuthInfoResponse} - } - - for i := uint32(0); i < n; i++ { - ans, rest, ok := parseString(packet) - if !ok { - return nil, &ParseError{msgUserAuthInfoResponse} - } - - answers = append(answers, string(ans)) - packet = rest - } - if len(packet) != 0 { - return nil, errors.New("ssh: junk at end of message") - } - - return answers, nil -} - -const defaultWindowSize = 32768 - -// Accept reads and processes messages on a ServerConn. It must be called -// in order to demultiplex messages to any resulting Channels. -func (s *ServerConn) Accept() (Channel, error) { - // TODO(dfc) s.lock is not held here so visibility of s.err is not guaranteed. - if s.err != nil { - return nil, s.err - } - - for { - packet, err := s.transport.readPacket() - if err != nil { - - s.lock.Lock() - s.err = err - s.lock.Unlock() - - // TODO(dfc) s.lock protects s.channels but isn't being held here. - for _, c := range s.channels { - c.setDead() - c.handleData(nil) - } - - return nil, err - } - - switch packet[0] { - case msgChannelData: - if len(packet) < 9 { - // malformed data packet - return nil, ParseError{msgChannelData} - } - remoteId := binary.BigEndian.Uint32(packet[1:5]) - s.lock.Lock() - c, ok := s.channels[remoteId] - if !ok { - s.lock.Unlock() - continue - } - if length := binary.BigEndian.Uint32(packet[5:9]); length > 0 { - packet = packet[9:] - c.handleData(packet[:length]) - } - s.lock.Unlock() - default: - decoded, err := decode(packet) - if err != nil { - return nil, err - } - switch msg := decoded.(type) { - case *channelOpenMsg: - if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { - return nil, errors.New("ssh: invalid MaxPacketSize from peer") - } - c := &serverChan{ - channel: channel{ - packetConn: s.transport, - remoteId: msg.PeersId, - remoteWin: window{Cond: newCond()}, - maxPacket: msg.MaxPacketSize, - }, - chanType: msg.ChanType, - extraData: msg.TypeSpecificData, - myWindow: defaultWindowSize, - serverConn: s, - cond: newCond(), - pendingData: make([]byte, defaultWindowSize), - } - c.remoteWin.add(msg.PeersWindow) - s.lock.Lock() - c.localId = s.nextChanId - s.nextChanId++ - s.channels[c.localId] = c - s.lock.Unlock() - return c, nil - - case *channelRequestMsg: - s.lock.Lock() - c, ok := s.channels[msg.PeersId] - if !ok { - s.lock.Unlock() - continue - } - c.handlePacket(msg) - s.lock.Unlock() - - case *windowAdjustMsg: - s.lock.Lock() - c, ok := s.channels[msg.PeersId] - if !ok { - s.lock.Unlock() - continue - } - c.handlePacket(msg) - s.lock.Unlock() - - case *channelEOFMsg: - s.lock.Lock() - c, ok := s.channels[msg.PeersId] - if !ok { - s.lock.Unlock() - continue - } - c.handlePacket(msg) - s.lock.Unlock() - - case *channelCloseMsg: - s.lock.Lock() - c, ok := s.channels[msg.PeersId] - if !ok { - s.lock.Unlock() - continue - } - c.handlePacket(msg) - s.lock.Unlock() - - case *globalRequestMsg: - if msg.WantReply { - if err := s.transport.writePacket([]byte{msgRequestFailure}); err != nil { - return nil, err - } - } - - case *kexInitMsg: - s.lock.Lock() - if err := s.clientInitHandshake(msg, packet); err != nil { - s.lock.Unlock() - return nil, err - } - s.lock.Unlock() - case *disconnectMsg: - return nil, io.EOF - default: - // Unknown message. Ignore. - } - } - } - - panic("unreachable") -} - -// A Listener implements a network listener (net.Listener) for SSH connections. -type Listener struct { - listener net.Listener - config *ServerConfig -} - -// Addr returns the listener's network address. -func (l *Listener) Addr() net.Addr { - return l.listener.Addr() -} - -// Close closes the listener. -func (l *Listener) Close() error { - return l.listener.Close() -} - -// Accept waits for and returns the next incoming SSH connection. -// The receiver should call Handshake() in another goroutine -// to avoid blocking the accepter. -func (l *Listener) Accept() (*ServerConn, error) { - c, err := l.listener.Accept() - if err != nil { - return nil, err - } - return Server(c, l.config), nil -} - -// Listen creates an SSH listener accepting connections on -// the given network address using net.Listen. -func Listen(network, addr string, config *ServerConfig) (*Listener, error) { - l, err := net.Listen(network, addr) - if err != nil { - return nil, err - } - return &Listener{ - l, - config, - }, nil -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server_terminal.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server_terminal.go deleted file mode 100644 index 708a9159..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/server_terminal.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -// A Terminal is capable of parsing and generating virtual terminal -// data from an SSH client. -type Terminal interface { - ReadLine() (line string, err error) - SetSize(x, y int) - Write([]byte) (int, error) -} - -// ServerTerminal contains the state for running a terminal that is capable of -// reading lines of input. -type ServerTerminal struct { - Term Terminal - Channel Channel -} - -// parsePtyRequest parses the payload of the pty-req message and extracts the -// dimensions of the terminal. See RFC 4254, section 6.2. -func parsePtyRequest(s []byte) (width, height int, ok bool) { - _, s, ok = parseString(s) - if !ok { - return - } - width32, s, ok := parseUint32(s) - if !ok { - return - } - height32, _, ok := parseUint32(s) - width = int(width32) - height = int(height32) - if width < 1 { - ok = false - } - if height < 1 { - ok = false - } - return -} - -func (ss *ServerTerminal) Write(buf []byte) (n int, err error) { - return ss.Term.Write(buf) -} - -// ReadLine returns a line of input from the terminal. -func (ss *ServerTerminal) ReadLine() (line string, err error) { - for { - if line, err = ss.Term.ReadLine(); err == nil { - return - } - - req, ok := err.(ChannelRequest) - if !ok { - return - } - - ok = false - switch req.Request { - case "pty-req": - var width, height int - width, height, ok = parsePtyRequest(req.Payload) - ss.Term.SetSize(width, height) - case "shell": - ok = true - if len(req.Payload) > 0 { - // We don't accept any commands, only the default shell. - ok = false - } - case "env": - ok = true - } - if req.WantReply { - ss.Channel.AckRequest(ok) - } - } - panic("unreachable") -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_linux.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_linux.go deleted file mode 100644 index 283144b7..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_linux.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux - -package terminal - -import "syscall" - -const ioctlReadTermios = syscall.TCGETS -const ioctlWriteTermios = syscall.TCSETS diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/keys_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/keys_test.go deleted file mode 100644 index b1164220..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/keys_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package test - -import ( - "reflect" - "strings" - "testing" - - "code.google.com/p/go.crypto/ssh" -) - -var ( - validKey = `AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJ` + - `yUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk` + - `9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+` + - `UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV` + - `86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69` + - `Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflD` + - `qCqAE4j+doagSsIfC1T2T` - - authWithOptions = []string{ - `# comments to ignore before any keys...`, - ``, - `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + validKey + ` user@host`, - `# comments to ignore, along with a blank line`, - ``, - `env="HOME=/home/root2" ssh-rsa ` + validKey + ` user2@host2`, - ``, - `# more comments, plus a invalid entry`, - `ssh-rsa data-that-will-not-parse user@host3`, - } - - authOptions = strings.Join(authWithOptions, "\n") - authWithCRLF = strings.Join(authWithOptions, "\r\n") - authInvalid = []byte(`ssh-rsa`) - authWithQuotedCommaInEnv = []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`) - authWithQuotedSpaceInEnv = []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + validKey + ` user@host`) - authWithQuotedQuoteInEnv = []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + validKey + ` user@host`) - - authWithDoubleQuotedQuote = []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + validKey + "\t" + `user@host`) - authWithInvalidSpace = []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + validKey + ` user@host -#more to follow but still no valid keys`) - authWithMissingQuote = []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + validKey + ` user@host -env="HOME=/home/root",shared-control ssh-rsa ` + validKey + ` user@host`) - - testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAxF/3T7uD5rb4Cty2vc4qAhA6yclK+sRCCuz6/qy4MnXKlk1P -5Le8O4CozsOL784B34ypdPQlsr4G/suXQok5PTMSPnqxjYbN6cGqEvhGrwG2sAe4 -hKmMk3qd2GiSvuESeDl+2ZVzACDK0y/lFayvPbeeoQpBWGgIKN1WPs+q2/292wwW -LRNWNrUuwt2ru92g4Hm/abCK0lfOrnCgU5eV+thZ2IshnfvsQpyweri8YpjOTil3 -y8yUDUv0MmcpNdoNw/MuvV8NRswkil9btfjEG6Mn9ByXBtq8lAix3XA1aaQKch8d -ji6ud4ZZEP8sXX5Q6gqgBOI/naGoErCHwtU9kwIDAQABAoIBAFJRKAp0QEZmTHPB -MZk+4r0asIoFpziXLFgIHu7C2DPOzK1Umzj1DCKlPB3wOqi7Ym2jOSWdcnAK2EPW -dAGgJC5TSkKGjAcXixmB5RkumfKidUI0+lQh/puTurcMnvcEwglDkLkEvMBA/sSo -Pw9m486rOgOnmNzGPyViItURmD2+0yDdLl/vOsO/L1p76GCd0q0J3LqnmsQmawi7 -Zwj2Stm6BIrggG5GsF204Iet5219TYLo4g1Qb2AlJ9C8P1FtAWhMwJalDxH9Os2/ -KCDjnaq5n3bXbIU+3QjskjeVXL/Fnbhjnh4zs1EA7eHzl9dCGbcZ2LOimo2PRo8q -wVQmz4ECgYEA9dhiu74TxRVoaO5N2X+FsMzRO8gZdP3Z9IrV4jVN8WT4Vdp0snoF -gkVkqqbQUNKUb5K6B3Js/qNKfcjLbCNq9fewTcT6WsHQdtPbX/QA6Pa2Z29wrlA2 -wrIYaAkmVaHny7wsOmgX01aOnuf2MlUnksK43sjZHdIo/m+sDKwwY1cCgYEAzHx4 -mwUDMdRF4qpDKJhthraBNejRextNQQYsHVnNaMwZ4aeQcH5l85Cgjm7VpGlbVyBQ -h4zwFvllImp3D2U3mjVkV8Tm9ID98eWvw2YDzBnS3P3SysajD23Z+BXSG9GNv/8k -oAm+bVlvnJy4haK2AcIMk1YFuDuAOmy73abk7iUCgYEAj4qVM1sq/eKfAM1LJRfg -/jbIX+hYfMePD8pUUWygIra6jJ4tjtvSBZrwyPb3IImjY3W/KoP0AcVjxAeORohz -dkP1a6L8LiuFxSuzpdW5BkyuebxGhXCOWKVVvMDC4jLTPVCUXlHSv3GFemCjjgXM -QlNxT5rjsha4Gr8nLIsJAacCgYA4VA1Q/pd7sXKy1p37X8nD8yAyvnh+Be5I/C9I -woUP2jFC9MqYAmmJJ4ziz2swiAkuPeuQ+2Tjnz2ZtmQnrIUdiJmkh8vrDGFnshKx -q7deELsCPzVCwGcIiAUkDra7DQWUHu9y2lxHePyC0rUNst2aLF8UcvzOXC2danhx -vViQtQKBgCmZ7YavE/GNWww8N3xHBJ6UPmUuhQlnAbgNCcdyz30MevBg/JbyUTs2 -slftTH15QusJ1UoITnnZuFJ40LqDvh8UhiK09ffM/IbUx839/m2vUOdFZB/WNn9g -Cy0LzddU4KE8JZ/tlk68+hM5fjLLA0aqSunaql5CKfplwLu8x1hL ------END RSA PRIVATE KEY----- -` - keys = map[string]string{ - "ssh_host_dsa_key": `-----BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQDe2SIKvZdBp+InawtSXH0NotiMPhm3udyu4hh/E+icMz264kDX -v+sV7ddnSQGQWZ/eVU7Jtx29dCMD1VlFpEd7yGKzmdwJIeA+YquNWoqBRQEJsWWS -7Fsfvv83dA/DTNIQfOY3+TIs6Mb9vagbgQMU3JUWEhbLE9LCEU6UwwRlpQIVAL4p -JF83SwpE8Jx6KnDpR89npkl/AoGAAy00TdDnAXvStwrZiAFbjZi8xDmPa9WwpfhJ -Rkno45TthDLrS+WmqY8/LTwlqZdOBtoBAynMJfKkUiZM21lWWpL1hRKYdwBlIBy5 -XdR2/6wcPSuZ0tCQhDBTstX0Q3P1j198KGKvzy7q9vILKQwtSRqLS1y4JJERafdO -E+9CnGwCgYBz0WwBe2EZtGhGhBdnelTIBeo7PIsr0PzqxQj+dc8PBl8K9FfhRyOp -U39stUvoUxE9vaIFrY1P5xENjLFnPf+hlcuf40GUWEssW9YWPOaBp8afa9hY5Sxs -pvNR6eZFEFOJnx/ZgcA4g+vbrgGi5cM0W470mbGw2CkfJQUafdoIgAIUF+2I9kZe -2FTBuC9uacqczDlc+0k= ------END DSA PRIVATE KEY-----`, - "ssh_host_rsa_key": `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAuf76Ue2Wtae9oDtaS6rIJgO7iCFTsZUTW9LBsvx/2nli6jKU -d9tUbBRzgdbnRLJ32UljXhERuB/axlrX8/lBzUZ+oYiM0KkEEOXY1z/bcMxdRxGF -XHuf4uXvyC2XyA4+ZvBeS4j1QFyIHZ62o7gAlKMTjiek3B4AQEJAlCLmhH3jB8wc -K/IYXAOlNGM5G44/ZLQpTi8diOV6DLs7tJ7rtEQedOEJfZng5rwp0USFkqcbfDbe -9/hk0J32jZvOtZNBokYtBb4YEdIiWBzzNtHzU3Dzw61+TKVXaH5HaIvzL9iMrw9f -kJbJyogfZk9BJfemEN+xqP72jlhE8LXNhpTxFQIDAQABAoIBAHbdf+Y5+5XuNF6h -b8xpwW2h9whBnDYiOnP1VfroKWFbMB7R4lZS4joMO+FfkP8zOyqvHwTvza4pFWys -g9SUmDvy8FyVYsC7MzEFYzX0xm3o/Te898ip7P1Zy4rXsGeWysSImwqU5X+TYx3i -33/zyNM1APtZVJ+jwK9QZ+sD/uPuZK2yS03HGSMZq6ebdoOSaYhluKrxXllSLO1J -KJxDiDdy2lEFw0W8HcI3ly1lg6OI+TRqqaCcLVNF4fNJmYIFM+2VEI9BdgynIh0Q -pMZlJKgaEBcSqCymnTK81ohYD1cV4st2B0km3Sw35Rl04Ij5ITeiya3hp8VfE6UY -PljkA6UCgYEA4811FTFj+kzNZ86C4OW1T5sM4NZt8gcz6CSvVnl+bDzbEOMMyzP7 -2I9zKsR5ApdodH2m8d+RUw1Oe0bNGW5xig/DH/hn9lLQaO52JAi0we8A94dUUMSq -fUk9jKZEXpP/MlfTdJaPos9mxT7z8jREQxIiqH9AV0rLVDOCfDbSWj8CgYEA0QTE -IAUuki3UUqYKzLQrh/QmhY5KTx5amNW9XZ2VGtJvDPJrtBSBZlPEuXZAc4eBWEc7 -U3Y9QwsalzupU6Yi6+gmofaXs8xJnj+jKth1DnJvrbLLGlSmf2Ijnwt22TyFUOtt -UAknpjHutDjQPf7pUGWaCPgwwKFsdB8EBjpJF6sCgYAfXesBQAvEK08dPBJJZVfR -3kenrd71tIgxLtv1zETcIoUHjjv0vvOunhH9kZAYC0EWyTZzl5UrGmn0D4uuNMbt -e74iaNHn2P9Zc3xQ+eHp0j8P1lKFzI6tMaiH9Vz0qOw6wl0bcJ/WizhbcI+migvc -MGMVUHBLlMDqly0gbWwJgQKBgQCgtb9ut01FjANSwORQ3L8Tu3/a9Lrh9n7GQKFn -V4CLrP1BwStavOF5ojMCPo/zxF6JV8ufsqwL3n/FhFP/QyBarpb1tTqTPiHkkR2O -Ffx67TY9IdnUFv4lt3mYEiKBiW0f+MSF42Qe/wmAfKZw5IzUCirTdrFVi0huSGK5 -vxrwHQKBgHZ7RoC3I2f6F5fflA2ZAe9oJYC7XT624rY7VeOBwK0W0F47iV3euPi/ -pKvLIBLcWL1Lboo+girnmSZtIYg2iLS3b4T9VFcKWg0y4AVwmhMWe9jWIltfWAAX -9l0lNikMRGAx3eXudKXEtbGt3/cUzPVaQUHy5LiBxkxnFxgaJPXs ------END RSA PRIVATE KEY-----`, - "ssh_host_ecdsa_key": `-----BEGIN EC PRIVATE KEY----- -MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49 -AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ -6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA== ------END EC PRIVATE KEY-----`, - "authorized_keys": `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEX/dPu4PmtvgK3La9zioCEDrJyUr6xEIK7Pr+rLgydcqWTU/kt7w7gKjOw4vvzgHfjKl09CWyvgb+y5dCiTk9MxI+erGNhs3pwaoS+EavAbawB7iEqYyTep3YaJK+4RJ4OX7ZlXMAIMrTL+UVrK89t56hCkFYaAgo3VY+z6rb/b3bDBYtE1Y2tS7C3au73aDgeb9psIrSV86ucKBTl5X62FnYiyGd++xCnLB6uLximM5OKXfLzJQNS/QyZyk12g3D8y69Xw1GzCSKX1u1+MQboyf0HJcG2ryUCLHdcDVppApyHx2OLq53hlkQ/yxdflDqCqAE4j+doagSsIfC1T2T user@host`, - } -) - -func TestMarshalParsePublicKey(t *testing.T) { - pub := getTestPublicKey(t) - - authKeys := ssh.MarshalAuthorizedKey(pub) - actualFields := strings.Fields(string(authKeys)) - if len(actualFields) == 0 { - t.Fatalf("failed authKeys: %v", authKeys) - } - - // drop the comment - expectedFields := strings.Fields(keys["authorized_keys"])[0:2] - - if !reflect.DeepEqual(actualFields, expectedFields) { - t.Errorf("got %v, expected %v", actualFields, expectedFields) - } - - actPub, _, _, _, ok := ssh.ParseAuthorizedKey([]byte(keys["authorized_keys"])) - if !ok { - t.Fatalf("cannot parse %v", keys["authorized_keys"]) - } - if !reflect.DeepEqual(actPub, pub) { - t.Errorf("got %v, expected %v", actPub, pub) - } -} - -type authResult struct { - pubKey interface{} //*rsa.PublicKey - options []string - comments string - rest string - ok bool -} - -func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) { - rest := authKeys - var values []authResult - for len(rest) > 0 { - var r authResult - r.pubKey, r.comments, r.options, rest, r.ok = ssh.ParseAuthorizedKey(rest) - r.rest = string(rest) - values = append(values, r) - } - - if !reflect.DeepEqual(values, expected) { - t.Errorf("got %q, expected %q", values, expected) - } - -} - -func getTestPublicKey(t *testing.T) ssh.PublicKey { - priv, err := ssh.ParsePrivateKey([]byte(testClientPrivateKey)) - if err != nil { - t.Fatalf("ParsePrivateKey: %v", err) - } - - return priv.PublicKey() -} - -func TestAuth(t *testing.T) { - pub := getTestPublicKey(t) - rest2 := strings.Join(authWithOptions[3:], "\n") - rest3 := strings.Join(authWithOptions[6:], "\n") - testAuthorizedKeys(t, []byte(authOptions), []authResult{ - {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, - {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, - {nil, nil, "", "", false}, - }) -} - -func TestAuthWithCRLF(t *testing.T) { - pub := getTestPublicKey(t) - rest2 := strings.Join(authWithOptions[3:], "\r\n") - rest3 := strings.Join(authWithOptions[6:], "\r\n") - testAuthorizedKeys(t, []byte(authWithCRLF), []authResult{ - {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, - {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, - {nil, nil, "", "", false}, - }) -} - -func TestAuthWithQuotedSpaceInEnv(t *testing.T) { - pub := getTestPublicKey(t) - testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{ - {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true}, - }) -} - -func TestAuthWithQuotedCommaInEnv(t *testing.T) { - pub := getTestPublicKey(t) - testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{ - {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true}, - }) -} - -func TestAuthWithQuotedQuoteInEnv(t *testing.T) { - pub := getTestPublicKey(t) - testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{ - {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true}, - }) - - testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{ - {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true}, - }) - -} - -func TestAuthWithInvalidSpace(t *testing.T) { - testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{ - {nil, nil, "", "", false}, - }) -} - -func TestAuthWithMissingQuote(t *testing.T) { - pub := getTestPublicKey(t) - testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{ - {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true}, - }) -} - -func TestInvalidEntry(t *testing.T) { - _, _, _, _, ok := ssh.ParseAuthorizedKey(authInvalid) - if ok { - t.Errorf("Expected invalid entry, returned valid entry") - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/tcpip_test.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/tcpip_test.go deleted file mode 100644 index ee06b60b..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/tcpip_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !windows - -package test - -// direct-tcpip functional tests - -import ( - "net" - "net/http" - "testing" -) - -func TestTCPIPHTTP(t *testing.T) { - // google.com will generate at least one redirect, possibly three - // depending on your location. - doTest(t, "http://google.com") -} - -func TestTCPIPHTTPS(t *testing.T) { - doTest(t, "https://encrypted.google.com/") -} - -func doTest(t *testing.T, url string) { - server := newServer(t) - defer server.Shutdown() - conn := server.Dial(clientConfig()) - defer conn.Close() - - tr := &http.Transport{ - Dial: func(n, addr string) (net.Conn, error) { - return conn.Dial(n, addr) - }, - } - client := &http.Client{ - Transport: tr, - } - resp, err := client.Get(url) - if err != nil { - t.Fatalf("unable to proxy: %s", err) - } - // got a body without error - t.Log(resp) -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport.go b/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport.go deleted file mode 100644 index 46fa2626..00000000 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport.go +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package ssh - -import ( - "bufio" - "crypto/cipher" - "crypto/subtle" - "encoding/binary" - "errors" - "hash" - "io" - "net" - "sync" -) - -const ( - packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. - - // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations - // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC - // indicates implementations SHOULD be able to handle larger packet sizes, but then - // waffles on about reasonable limits. - // - // OpenSSH caps their maxPacket at 256kb so we choose to do the same. - maxPacket = 256 * 1024 -) - -// packetConn represents a transport that implements packet based -// operations. -type packetConn interface { - // Encrypt and send a packet of data to the remote peer. - writePacket(packet []byte) error - - // Read a packet from the connection - readPacket() ([]byte, error) - - // Close closes the write-side of the connection. - Close() error -} - -// transport represents the SSH connection to the remote peer. -type transport struct { - reader - writer - - net.Conn - - // Initial H used for the session ID. Once assigned this does - // not change, even during subsequent key exchanges. - sessionID []byte -} - -// reader represents the incoming connection state. -type reader struct { - io.Reader - common -} - -// writer represents the outgoing connection state. -type writer struct { - sync.Mutex // protects writer.Writer from concurrent writes - *bufio.Writer - rand io.Reader - common -} - -// prepareKeyChange sets up key material for a keychange. The key changes in -// both directions are triggered by reading and writing a msgNewKey packet -// respectively. -func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { - t.writer.cipherAlgo = algs.wCipher - t.writer.macAlgo = algs.wMAC - t.writer.compressionAlgo = algs.wCompression - - t.reader.cipherAlgo = algs.rCipher - t.reader.macAlgo = algs.rMAC - t.reader.compressionAlgo = algs.rCompression - - if t.sessionID == nil { - t.sessionID = kexResult.H - } - - kexResult.SessionID = t.sessionID - t.reader.pendingKeyChange <- kexResult - t.writer.pendingKeyChange <- kexResult - return nil -} - -// common represents the cipher state needed to process messages in a single -// direction. -type common struct { - seqNum uint32 - mac hash.Hash - cipher cipher.Stream - - cipherAlgo string - macAlgo string - compressionAlgo string - - dir direction - pendingKeyChange chan *kexResult -} - -// Read and decrypt a single packet from the remote peer. -func (r *reader) readPacket() ([]byte, error) { - var lengthBytes = make([]byte, 5) - var macSize uint32 - if _, err := io.ReadFull(r, lengthBytes); err != nil { - return nil, err - } - - r.cipher.XORKeyStream(lengthBytes, lengthBytes) - - if r.mac != nil { - r.mac.Reset() - seqNumBytes := []byte{ - byte(r.seqNum >> 24), - byte(r.seqNum >> 16), - byte(r.seqNum >> 8), - byte(r.seqNum), - } - r.mac.Write(seqNumBytes) - r.mac.Write(lengthBytes) - macSize = uint32(r.mac.Size()) - } - - length := binary.BigEndian.Uint32(lengthBytes[0:4]) - paddingLength := uint32(lengthBytes[4]) - - if length <= paddingLength+1 { - return nil, errors.New("ssh: invalid packet length, packet too small") - } - - if length > maxPacket { - return nil, errors.New("ssh: invalid packet length, packet too large") - } - - packet := make([]byte, length-1+macSize) - if _, err := io.ReadFull(r, packet); err != nil { - return nil, err - } - mac := packet[length-1:] - r.cipher.XORKeyStream(packet, packet[:length-1]) - - if r.mac != nil { - r.mac.Write(packet[:length-1]) - if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 { - return nil, errors.New("ssh: MAC failure") - } - } - - r.seqNum++ - packet = packet[:length-paddingLength-1] - - if len(packet) > 0 && packet[0] == msgNewKeys { - select { - case k := <-r.pendingKeyChange: - if err := r.setupKeys(r.dir, k); err != nil { - return nil, err - } - default: - return nil, errors.New("ssh: got bogus newkeys message.") - } - } - return packet, nil -} - -// Read and decrypt next packet discarding debug and noop messages. -func (t *transport) readPacket() ([]byte, error) { - for { - packet, err := t.reader.readPacket() - if err != nil { - return nil, err - } - if len(packet) == 0 { - return nil, errors.New("ssh: zero length packet") - } - - if packet[0] != msgIgnore && packet[0] != msgDebug { - return packet, nil - } - } - panic("unreachable") -} - -// Encrypt and send a packet of data to the remote peer. -func (w *writer) writePacket(packet []byte) error { - changeKeys := len(packet) > 0 && packet[0] == msgNewKeys - - if len(packet) > maxPacket { - return errors.New("ssh: packet too large") - } - w.Mutex.Lock() - defer w.Mutex.Unlock() - - paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple - if paddingLength < 4 { - paddingLength += packetSizeMultiple - } - - length := len(packet) + 1 + paddingLength - lengthBytes := []byte{ - byte(length >> 24), - byte(length >> 16), - byte(length >> 8), - byte(length), - byte(paddingLength), - } - padding := make([]byte, paddingLength) - _, err := io.ReadFull(w.rand, padding) - if err != nil { - return err - } - - if w.mac != nil { - w.mac.Reset() - seqNumBytes := []byte{ - byte(w.seqNum >> 24), - byte(w.seqNum >> 16), - byte(w.seqNum >> 8), - byte(w.seqNum), - } - w.mac.Write(seqNumBytes) - w.mac.Write(lengthBytes) - w.mac.Write(packet) - w.mac.Write(padding) - } - - // TODO(dfc) lengthBytes, packet and padding should be - // subslices of a single buffer - w.cipher.XORKeyStream(lengthBytes, lengthBytes) - w.cipher.XORKeyStream(packet, packet) - w.cipher.XORKeyStream(padding, padding) - - if _, err := w.Write(lengthBytes); err != nil { - return err - } - if _, err := w.Write(packet); err != nil { - return err - } - if _, err := w.Write(padding); err != nil { - return err - } - - if w.mac != nil { - if _, err := w.Write(w.mac.Sum(nil)); err != nil { - return err - } - } - - w.seqNum++ - if err = w.Flush(); err != nil { - return err - } - - if changeKeys { - select { - case k := <-w.pendingKeyChange: - err = w.setupKeys(w.dir, k) - default: - panic("ssh: no key material for msgNewKeys") - } - } - return err -} - -func newTransport(conn net.Conn, rand io.Reader, isClient bool) *transport { - t := &transport{ - reader: reader{ - Reader: bufio.NewReader(conn), - common: common{ - cipher: noneCipher{}, - pendingKeyChange: make(chan *kexResult, 1), - }, - }, - writer: writer{ - Writer: bufio.NewWriter(conn), - rand: rand, - common: common{ - cipher: noneCipher{}, - pendingKeyChange: make(chan *kexResult, 1), - }, - }, - Conn: conn, - } - if isClient { - t.reader.dir = serverKeys - t.writer.dir = clientKeys - } else { - t.reader.dir = clientKeys - t.writer.dir = serverKeys - } - - return t -} - -type direction struct { - ivTag []byte - keyTag []byte - macKeyTag []byte -} - -// TODO(dfc) can this be made a constant ? -var ( - serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} - clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} -) - -// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as -// described in RFC 4253, section 6.4. direction should either be serverKeys -// (to setup server->client keys) or clientKeys (for client->server keys). -func (c *common) setupKeys(d direction, r *kexResult) error { - cipherMode := cipherModes[c.cipherAlgo] - macMode := macModes[c.macAlgo] - - iv := make([]byte, cipherMode.ivSize) - key := make([]byte, cipherMode.keySize) - macKey := make([]byte, macMode.keySize) - - h := r.Hash.New() - generateKeyMaterial(iv, d.ivTag, r.K, r.H, r.SessionID, h) - generateKeyMaterial(key, d.keyTag, r.K, r.H, r.SessionID, h) - generateKeyMaterial(macKey, d.macKeyTag, r.K, r.H, r.SessionID, h) - - c.mac = macMode.new(macKey) - - var err error - c.cipher, err = cipherMode.createCipher(key, iv) - return err -} - -// generateKeyMaterial fills out with key material generated from tag, K, H -// and sessionId, as specified in RFC 4253, section 7.2. -func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) { - var digestsSoFar []byte - - for len(out) > 0 { - h.Reset() - h.Write(K) - h.Write(H) - - if len(digestsSoFar) == 0 { - h.Write(tag) - h.Write(sessionId) - } else { - h.Write(digestsSoFar) - } - - digest := h.Sum(nil) - n := copy(out, digest) - out = out[n:] - if len(out) > 0 { - digestsSoFar = append(digestsSoFar, digest...) - } - } -} - -const packageVersion = "SSH-2.0-Go" - -// Sends and receives a version line. The versionLine string should -// be US ASCII, start with "SSH-2.0-", and should not include a -// newline. exchangeVersions returns the other side's version line. -func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { - // Contrary to the RFC, we do not ignore lines that don't - // start with "SSH-2.0-" to make the library usable with - // nonconforming servers. - for _, c := range versionLine { - // The spec disallows non US-ASCII chars, and - // specifically forbids null chars. - if c < 32 { - return nil, errors.New("ssh: junk character in version line") - } - } - if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { - return - } - - them, err = readVersion(rw) - return them, err -} - -// maxVersionStringBytes is the maximum number of bytes that we'll -// accept as a version string. RFC 4253 section 4.2 limits this at 255 -// chars -const maxVersionStringBytes = 255 - -// Read version string as specified by RFC 4253, section 4.2. -func readVersion(r io.Reader) ([]byte, error) { - versionString := make([]byte, 0, 64) - var ok bool - var buf [1]byte - - for len(versionString) < maxVersionStringBytes { - _, err := io.ReadFull(r, buf[:]) - if err != nil { - return nil, err - } - // The RFC says that the version should be terminated with \r\n - // but several SSH servers actually only send a \n. - if buf[0] == '\n' { - ok = true - break - } - - // non ASCII chars are disallowed, but we are lenient, - // since Go doesn't use null-terminated strings. - - // The RFC allows a comment after a space, however, - // all of it (version and comments) goes into the - // session hash. - versionString = append(versionString, buf[0]) - } - - if !ok { - return nil, errors.New("ssh: overflow reading version string") - } - - // There might be a '\r' on the end which we should remove. - if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { - versionString = versionString[:len(versionString)-1] - } - return versionString, nil -} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/AlekSi/pointer/pointer.go b/src/github.com/smira/aptly/_vendor/src/github.com/AlekSi/pointer/pointer.go new file mode 100644 index 00000000..b33c4ad5 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/AlekSi/pointer/pointer.go @@ -0,0 +1,53 @@ +package pointer + +func ToString(s string) *string { + return &s +} + +func ToUint(u uint) *uint { + return &u +} + +func ToUint8(u uint8) *uint8 { + return &u +} + +func ToUint16(u uint16) *uint16 { + return &u +} + +func ToUint32(u uint32) *uint32 { + return &u +} + +func ToUint64(u uint64) *uint64 { + return &u +} + +func ToInt(i int) *int { + return &i +} + +func ToInt8(i int8) *int8 { + return &i +} + +func ToInt16(i int16) *int16 { + return &i +} + +func ToInt32(i int32) *int32 { + return &i +} + +func ToInt64(i int64) *int64 { + return &i +} + +func ToFloat32(f float32) *float32 { + return &f +} + +func ToFloat64(f float64) *float64 { + return &f +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/README.md index 5e475e80..9a6f6b41 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/README.md +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/README.md @@ -57,6 +57,12 @@ bar.ShowTimeLeft = true // show average speed bar.ShowSpeed = true +// sets the width of the progress bar +bar.SetWith(80) + +// sets the width of the progress bar, but if terminal size smaller will be ignored +bar.SetMaxWith(80) + // convert output to readable format (like KB, MB) bar.SetUnits(pb.U_BYTES) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb.go b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb.go index 95ae95bb..bc9aa0e2 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb.go @@ -25,13 +25,21 @@ var ( // Create new progress bar object func New(total int) (pb *ProgressBar) { + return New64(int64(total)) +} + +// Create new progress bar object uding int64 as total +func New64(total int64) (pb *ProgressBar) { pb = &ProgressBar{ - Total: int64(total), - RefreshRate: DEFAULT_REFRESH_RATE, - ShowPercent: true, - ShowCounters: true, - ShowBar: true, - ShowTimeLeft: true, + Total: total, + RefreshRate: DEFAULT_REFRESH_RATE, + ShowPercent: true, + ShowCounters: true, + ShowBar: true, + ShowTimeLeft: true, + ShowFinalTime: true, + ManualUpdate: false, + currentValue: -1, } pb.Format(FORMAT) return @@ -59,13 +67,20 @@ type ProgressBar struct { RefreshRate time.Duration ShowPercent, ShowCounters bool ShowSpeed, ShowTimeLeft, ShowBar bool + ShowFinalTime bool Output io.Writer Callback Callback NotPrint bool Units int + Width int + ForceWidth bool + ManualUpdate bool - isFinish bool - startTime time.Time + isFinish int32 + startTime time.Time + currentValue int64 + + prefix, postfix string BarStart string BarEnd string @@ -82,7 +97,9 @@ func (pb *ProgressBar) Start() { pb.ShowTimeLeft = false pb.ShowPercent = false } - go pb.writer() + if !pb.ManualUpdate { + go pb.writer() + } } // Increment current value @@ -97,7 +114,23 @@ func (pb *ProgressBar) Set(current int) { // Add to current value func (pb *ProgressBar) Add(add int) int { - return int(atomic.AddInt64(&pb.current, int64(add))) + return int(pb.Add64(int64(add))) +} + +func (pb *ProgressBar) Add64(add int64) int64 { + return atomic.AddInt64(&pb.current, add) +} + +// Set prefix string +func (pb *ProgressBar) Prefix(prefix string) (bar *ProgressBar) { + pb.prefix = prefix + return pb +} + +// Set postfix string +func (pb *ProgressBar) Postfix(postfix string) (bar *ProgressBar) { + pb.postfix = postfix + return pb } // Set custom format for bar @@ -135,9 +168,25 @@ func (pb *ProgressBar) SetUnits(units int) (bar *ProgressBar) { return } +// Set max width, if width is bigger than terminal width, will be ignored +func (pb *ProgressBar) SetMaxWidth(width int) (bar *ProgressBar) { + bar = pb + pb.Width = width + pb.ForceWidth = false + return +} + +// Set bar width +func (pb *ProgressBar) SetWidth(width int) (bar *ProgressBar) { + bar = pb + pb.Width = width + pb.ForceWidth = true + return +} + // End print func (pb *ProgressBar) Finish() { - pb.isFinish = true + atomic.StoreInt32(&pb.isFinish, 1) pb.write(atomic.LoadInt64(&pb.current)) if !pb.NotPrint { fmt.Println() @@ -170,7 +219,8 @@ func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { } func (pb *ProgressBar) write(current int64) { - width, _ := terminalWidth() + width := pb.getWidth() + var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string // percents @@ -189,14 +239,17 @@ func (pb *ProgressBar) write(current int64) { } // time left - if pb.ShowTimeLeft && current > 0 { - fromStart := time.Now().Sub(pb.startTime) + fromStart := time.Now().Sub(pb.startTime) + if atomic.LoadInt32(&pb.isFinish) != 0 { + if pb.ShowFinalTime { + left := (fromStart / time.Second) * time.Second + timeLeftBox = left.String() + } + } else if pb.ShowTimeLeft && current > 0 { perEntry := fromStart / time.Duration(current) left := time.Duration(pb.Total-current) * perEntry left = (left / time.Second) * time.Second - if left > 0 { - timeLeftBox = left.String() - } + timeLeftBox = left.String() } // speed @@ -208,7 +261,7 @@ func (pb *ProgressBar) write(current int64) { // bar if pb.ShowBar { - size := width - len(countersBox+pb.BarStart+pb.BarEnd+percentBox+timeLeftBox+speedBox) + size := width - len(countersBox+pb.BarStart+pb.BarEnd+percentBox+timeLeftBox+speedBox+pb.prefix+pb.postfix) if size > 0 { curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) emptCount := size - curCount @@ -230,17 +283,15 @@ func (pb *ProgressBar) write(current int64) { } // check len - out = countersBox + barBox + percentBox + speedBox + timeLeftBox + out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix if len(out) < width { end = strings.Repeat(" ", width-len(out)) } - out = countersBox + barBox + percentBox + speedBox + timeLeftBox - // and print! switch { case pb.Output != nil: - fmt.Fprint(pb.Output, out+end) + fmt.Fprint(pb.Output, "\r"+out+end) case pb.Callback != nil: pb.Callback(out + end) case !pb.NotPrint: @@ -248,18 +299,36 @@ func (pb *ProgressBar) write(current int64) { } } +func (pb *ProgressBar) getWidth() int { + if pb.ForceWidth { + return pb.Width + } + + width := pb.Width + termWidth, _ := terminalWidth() + if width == 0 || termWidth <= width { + width = termWidth + } + + return width +} + +// Write the current state of the progressbar +func (pb *ProgressBar) Update() { + c := atomic.LoadInt64(&pb.current) + if c != pb.currentValue { + pb.write(c) + pb.currentValue = c + } +} + +// Internal loop for writing progressbar func (pb *ProgressBar) writer() { - var c, oc int64 - oc = -1 for { - if pb.isFinish { + if atomic.LoadInt32(&pb.isFinish) != 0 { break } - c = atomic.LoadInt64(&pb.current) - if c != oc { - pb.write(c) - oc = c - } + pb.Update() time.Sleep(pb.RefreshRate) } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_nix.go b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_nix.go index 1f606aaf..5db4e523 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_nix.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_nix.go @@ -1,35 +1,7 @@ -// +build linux darwin freebsd +// +build linux darwin freebsd netbsd openbsd package pb -import ( - "runtime" - "syscall" - "unsafe" -) +import "syscall" -const ( - TIOCGWINSZ = 0x5413 - TIOCGWINSZ_OSX = 1074295912 -) - -func bold(str string) string { - return "\033[1m" + str + "\033[0m" -} - -func terminalWidth() (int, error) { - w := new(window) - tio := syscall.TIOCGWINSZ - if runtime.GOOS == "darwin" { - tio = TIOCGWINSZ_OSX - } - res, _, err := syscall.Syscall(syscall.SYS_IOCTL, - uintptr(syscall.Stdin), - uintptr(tio), - uintptr(unsafe.Pointer(w)), - ) - if int(res) == -1 { - return 0, err - } - return int(w.Col), nil -} +const sys_ioctl = syscall.SYS_IOCTL diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_solaris.go b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_solaris.go new file mode 100644 index 00000000..ac31110d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_solaris.go @@ -0,0 +1,5 @@ +// +build solaris + +package pb + +const sys_ioctl = 54 \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_test.go index 2464ca78..75783d63 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_test.go @@ -1,7 +1,6 @@ package pb import ( - "fmt" "testing" ) @@ -12,6 +11,20 @@ func Test_IncrementAddsOne(t *testing.T) { actual := bar.Increment() if actual != expected { - t.Error(fmt.Sprintf("Expected {%d} was {%d}", expected, actual)) + t.Errorf("Expected {%d} was {%d}", expected, actual) } } + +func Test_Width(t *testing.T) { + count := 5000 + bar := New(count) + width := 100 + bar.SetWidth(100).Callback = func(out string) { + if len(out) != width { + t.Errorf("Bar width expected {%d} was {%d}", len(out), width) + } + } + bar.Start() + bar.Increment() + bar.Finish() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_x.go b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_x.go new file mode 100644 index 00000000..dd5f906e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/cheggaaa/pb/pb_x.go @@ -0,0 +1,46 @@ +// +build linux darwin freebsd netbsd openbsd solaris + +package pb + +import ( + "os" + "runtime" + "syscall" + "unsafe" +) + +const ( + TIOCGWINSZ = 0x5413 + TIOCGWINSZ_OSX = 1074295912 +) + +var tty *os.File + +func init() { + var err error + tty, err = os.Open("/dev/tty") + if err != nil { + tty = os.Stdin + } +} + +func bold(str string) string { + return "\033[1m" + str + "\033[0m" +} + +func terminalWidth() (int, error) { + w := new(window) + tio := syscall.TIOCGWINSZ + if runtime.GOOS == "darwin" { + tio = TIOCGWINSZ_OSX + } + res, _, err := syscall.Syscall(sys_ioctl, + tty.Fd(), + uintptr(tio), + uintptr(unsafe.Pointer(w)), + ) + if int(res) == -1 { + return 0, err + } + return int(w.Col), nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.gitignore b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.gitignore new file mode 100644 index 00000000..96c135f3 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.gitignore @@ -0,0 +1,2 @@ +Godeps/* +!Godeps/Godeps.json diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.travis.yml b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.travis.yml new file mode 100644 index 00000000..3d338331 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/.travis.yml @@ -0,0 +1,6 @@ +language: go +sudo: false +go: + - 1.3 + - 1.4 + - tip diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/AUTHORS.md b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/AUTHORS.md new file mode 100644 index 00000000..45c54387 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/AUTHORS.md @@ -0,0 +1,174 @@ +List of all the awesome people working to make Gin the best Web Framework in Go. + + + +##gin 0.x series authors + +**Original Developer:** Manu Martinez-Almeida (@manucorporat) +**Long-term Maintainer:** Javier Provecho (@javierprovecho) + +People and companies, who have contributed, in alphabetical order. + +**@achedeuzot (Klemen Sever)** +- Fix newline debug printing + + +**@adammck (Adam Mckaig)** +- Add MIT license + + +**@AlexanderChen1989 (Alexander)** +- Typos in README + + +**@alexandernyquist (Alexander Nyquist)** +- Using template.Must to fix multiple return issue +- ★ Added support for OPTIONS verb +- ★ Setting response headers before calling WriteHeader +- Improved documentation for model binding +- ★ Added Content.Redirect() +- ★ Added tons of Unit tests + + +**@austinheap (Austin Heap)** +- Added travis CI integration + + +**@andredublin (Andre Dublin)** +- Fix typo in comment + + +**@bredov (Ludwig Valda Vasquez)** +- Fix html templating in debug mode + + +**@bluele (Jun Kimura)** +- Fixes code examples in README + + +**@chad-russell** +- ★ Support for serializing gin.H into XML + + +**@dickeyxxx (Jeff Dickey)** +- Typos in README +- Add example about serving static files + + +**@dutchcoders (DutchCoders)** +- ★ Fix security bug that allows client to spoof ip +- Fix typo. r.HTMLTemplates -> SetHTMLTemplate + + +**@fmd (Fareed Dudhia)** +- Fix typo. SetHTTPTemplate -> SetHTMLTemplate + + +**@jammie-stackhouse (Jamie Stackhouse)** +- Add more shortcuts for router methods + + +**@jasonrhansen** +- Fix spelling and grammar errors in documentation + + +**@JasonSoft (Jason Lee)** +- Fix typo in comment + + +**@joiggama (Ignacio Galindo)** +- Add utf-8 charset header on renders + + +**@julienschmidt (Julien Schmidt)** +- gofmt the code examples + + +**@kelcecil (Kel Cecil)** +- Fix readme typo + + +**@kyledinh (Kyle Dinh)** +- Adds RunTLS() + + +**@LinusU (Linus Unnebäck)** +- Small fixes in README + + +**@loongmxbt (Saint Asky)** +- Fix typo in example + + +**@lucas-clemente (Lucas Clemente)** +- ★ work around path.Join removing trailing slashes from routes + + +**@mdigger (Dmitry Sedykh)** +- Fixes Form binding when content-type is x-www-form-urlencoded +- No repeat call c.Writer.Status() in gin.Logger +- Fixes Content-Type for json render + + +**@mirzac (Mirza Ceric)** +- Fix debug printing + + +**@mopemope (Yutaka Matsubara)** +- ★ Adds Godep support (Dependencies Manager) +- Fix variadic parameter in the flexible render API +- Fix Corrupted plain render +- Add Pluggable View Renderer Example + + +**@msemenistyi (Mykyta Semenistyi)** +- update Readme.md. Add code to String method + + +**@msoedov (Sasha Myasoedov)** +- ★ Adds tons of unit tests. + + +**@ngerakines (Nick Gerakines)** +- ★ Improves API, c.GET() doesn't panic +- Adds MustGet() method + + +**@r8k (Rajiv Kilaparti)** +- Fix Port usage in README. + + +**@RobAWilkinson (Robert Wilkinson)** +- Add example of forms and params + + +**@se77en (Damon Zhao)** +- Improve color logging + + +**@silasb (Silas Baronda)** +- Fixing quotes in README + + +**@SkuliOskarsson (Skuli Oskarsson)** +- Fixes some texts in README II + + +**@slimmy (Jimmy Pettersson)** +- Added messages for required bindings + + +**@smira (Andrey Smirnov)** +- Add support for ignored/unexported fields in binding + + +**@superalsrk (SRK.Lyu)** +- Update httprouter godeps + + +**@yosssi (Keiji Yoshida)** +- Fix link in README + + +**@yuyabee** +- Fixed README \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/CHANGELOG.md b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/CHANGELOG.md new file mode 100644 index 00000000..72c848b7 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/CHANGELOG.md @@ -0,0 +1,64 @@ +#Changelog + +###Gin 0.6 (Mar 7, 2015) + + +###Gin 0.5 (Feb 7, 2015) + +- [NEW] Content Negotiation +- [FIX] Solved security bug that allow a client to spoof ip +- [FIX] Fix unexported/ignored fields in binding + + +###Gin 0.4 (Aug 21, 2014) + +- [NEW] Development mode +- [NEW] Unit tests +- [NEW] Add Content.Redirect() +- [FIX] Deferring WriteHeader() +- [FIX] Improved documentation for model binding + + +###Gin 0.3 (Jul 18, 2014) + +- [PERFORMANCE] Normal log and error log are printed in the same call. +- [PERFORMANCE] Improve performance of NoRouter() +- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults. +- [NEW] Flexible rendering API +- [NEW] Add Context.File() +- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS +- [FIX] Rename NotFound404() to NoRoute() +- [FIX] Errors in context are purged +- [FIX] Adds HEAD method in Static file serving +- [FIX] Refactors Static() file serving +- [FIX] Using keyed initialization to fix app-engine integration +- [FIX] Can't unmarshal JSON array, #63 +- [FIX] Renaming Context.Req to Context.Request +- [FIX] Check application/x-www-form-urlencoded when parsing form + + +###Gin 0.2b (Jul 08, 2014) +- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead +- [NEW] Travis CI integration +- [NEW] Completely new logger +- [NEW] New API for serving static files. gin.Static() +- [NEW] gin.H() can be serialized into XML +- [NEW] Typed errors. Errors can be typed. Internet/external/custom. +- [NEW] Support for Godeps +- [NEW] Travis/Godocs badges in README +- [NEW] New Bind() and BindWith() methods for parsing request body. +- [NEW] Add Content.Copy() +- [NEW] Add context.LastError() +- [NEW] Add shorcut for OPTIONS HTTP method +- [FIX] Tons of README fixes +- [FIX] Header is written before body +- [FIX] BasicAuth() and changes API a little bit +- [FIX] Recovery() middleware only prints panics +- [FIX] Context.Get() does not panic anymore. Use MustGet() instead. +- [FIX] Multiple http.WriteHeader() in NotFound handlers +- [FIX] Engine.Run() panics if http server can't be setted up +- [FIX] Crash when route path doesn't start with '/' +- [FIX] Do not update header when status code is negative +- [FIX] Setting response headers before calling WriteHeader in context.String() +- [FIX] Add MIT license +- [FIX] Changes behaviour of ErrorLogger() and Logger() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/Godeps/Godeps.json b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/Godeps/Godeps.json new file mode 100644 index 00000000..2d43fc9f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/Godeps/Godeps.json @@ -0,0 +1,10 @@ +{ + "ImportPath": "github.com/gin-gonic/gin", + "GoVersion": "go1.3", + "Deps": [ + { + "ImportPath": "github.com/julienschmidt/httprouter", + "Rev": "00ce1c6a267162792c367acc43b1681a884e1872" + } + ] +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/LICENSE new file mode 100644 index 00000000..1ff7f370 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Manuel Martínez-Almeida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/README.md new file mode 100644 index 00000000..3c6ca7ee --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/README.md @@ -0,0 +1,536 @@ +#Gin Web Framework [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) + +Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. + +![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) + +``` +$ cat test.go +``` +```go +package main + +import "github.com/gin-gonic/gin" + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + c.String(200, "hello world") + }) + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + router.POST("/submit", func(c *gin.Context) { + c.String(401, "not authorized") + }) + router.PUT("/error", func(c *gin.Context) { + c.String(500, "and error hapenned :(") + }) + router.Run(":8080") +} +``` + +##Gin is new, will it be supported? + +Yes, Gin is an internal tool of [Manu](https://github.com/manucorporat) and [Javi](https://github.com/javierprovecho) for many of our projects/start-ups. We developed it and we are going to continue using and improve it. + + +##Roadmap for v1.0 +- [ ] Ask our designer for a cool logo +- [ ] Add tons of unit tests +- [ ] Add internal benchmarks suite +- [ ] More powerful validation API +- [ ] Improve documentation +- [ ] Add Swagger support +- [x] Stable API +- [x] Improve logging system +- [x] Improve JSON/XML validation using bindings +- [x] Improve XML support +- [x] Flexible rendering system +- [x] Add more cool middlewares, for example redis caching (this also helps developers to understand the framework). +- [x] Continuous integration +- [x] Performance improments, reduce allocation and garbage collection overhead +- [x] Fix bugs + + + +## Start using it +Obviously, you need to have Git and Go already installed to run Gin. +Run this in your terminal + +``` +go get github.com/gin-gonic/gin +``` +Then import it in your Go code: + +``` +import "github.com/gin-gonic/gin" +``` + + +##Community +If you'd like to help out with the project, there's a mailing list and IRC channel where Gin discussions normally happen. + +* IRC + * [irc.freenode.net #getgin](irc://irc.freenode.net:6667/getgin) + * [Webchat](http://webchat.freenode.net?randomnick=1&channels=%23getgin) +* Mailing List + * Subscribe: [getgin@librelist.org](mailto:getgin@librelist.org) + * [Archives](http://librelist.com/browser/getgin/) + + +##API Examples + +#### Create most basic PING/PONG HTTP endpoint +```go +package main + +import "github.com/gin-gonic/gin" + +func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Using GET, POST, PUT, PATCH, DELETE and OPTIONS + +```go +func main() { + // Creates a gin router + logger and recovery (crash-free) middlewares + r := gin.Default() + + r.GET("/someGet", getting) + r.POST("/somePost", posting) + r.PUT("/somePut", putting) + r.DELETE("/someDelete", deleting) + r.PATCH("/somePatch", patching) + r.HEAD("/someHead", head) + r.OPTIONS("/someOptions", options) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Parameters in path + +```go +func main() { + r := gin.Default() + + // This handler will match /user/john but will not match neither /user/ or /user + r.GET("/user/:name", func(c *gin.Context) { + name := c.Params.ByName("name") + message := "Hello "+name + c.String(200, message) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/join/ + r.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Params.ByName("name") + action := c.Params.ByName("action") + message := name + " is " + action + c.String(200, message) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` +###Form parameters +```go +func main() { + r := gin.Default() + + // This will respond to urls like search?firstname=Jane&lastname=Doe + r.GET("/search", func(c *gin.Context) { + // You need to call ParseForm() on the request to receive url and form params first + c.Request.ParseForm() + + firstname := c.Request.Form.Get("firstname") + lastname := c.Request.Form.get("lastname") + + message := "Hello "+ firstname + lastname + c.String(200, message) + }) + r.Run(":8080") +} +``` + +#### Grouping routes +```go +func main() { + r := gin.Default() + + // Simple group: v1 + v1 := r.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := r.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + + +#### Blank Gin without middlewares by default + +Use + +```go +r := gin.New() +``` +instead of + +```go +r := gin.Default() +``` + + +#### Using middlewares +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middlewares + r.Use(gin.Logger()) + r.Use(gin.Recovery()) + + // Per route middlewares, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same than: + authorized := r.Group("/") + // per group middlewares! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use BindWith. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, the current request will fail with an error. + +```go +// Binding from JSON +type LoginJSON struct { + User string `json:"user" binding:"required"` + Password string `json:"password" binding:"required"` +} + +// Binding from form values +type LoginForm struct { + User string `form:"user" binding:"required"` + Password string `form:"password" binding:"required"` +} + +func main() { + r := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + r.POST("/loginJSON", func(c *gin.Context) { + var json LoginJSON + + c.Bind(&json) // This will infer what binder to use depending on the content-type header. + if json.User == "manu" && json.Password == "123" { + c.JSON(200, gin.H{"status": "you are logged in"}) + } else { + c.JSON(401, gin.H{"status": "unauthorized"}) + } + }) + + // Example for binding a HTML form (user=manu&password=123) + r.POST("/loginHTML", func(c *gin.Context) { + var form LoginForm + + c.BindWith(&form, binding.Form) // You can also specify which binder to use. We support binding.Form, binding.JSON and binding.XML. + if form.User == "manu" && form.Password == "123" { + c.JSON(200, gin.H{"status": "you are logged in"}) + } else { + c.JSON(401, gin.H{"status": "unauthorized"}) + } + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### XML and JSON rendering + +```go +func main() { + r := gin.Default() + + // gin.H is a shortcut for map[string]interface{} + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "hey", "status": 200}) + }) + + r.GET("/moreJSON", func(c *gin.Context) { + // You also can use a struct + var msg struct { + Name string `json:"user"` + Message string + Number int + } + msg.Name = "Lena" + msg.Message = "hey" + msg.Number = 123 + // Note that msg.Name becomes "user" in the JSON + // Will output : {"user": "Lena", "Message": "hey", "Number": 123} + c.JSON(200, msg) + }) + + r.GET("/someXML", func(c *gin.Context) { + c.XML(200, gin.H{"message": "hey", "status": 200}) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +####Serving static files + +Use Engine.ServeFiles(path string, root http.FileSystem): + +```go +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +Note: this will use `httpNotFound` instead of the Router's `NotFound` handler. + +####HTML rendering + +Using LoadHTMLTemplates() + +```go +func main() { + r := gin.Default() + r.LoadHTMLGlob("templates/*") + r.GET("/index", func(c *gin.Context) { + obj := gin.H{"title": "Main website"} + c.HTML(200, "index.tmpl", obj) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` +```html +

+ {{ .title }} +

+``` + +You can also use your own html template render + +```go +import "html/template" + +func main() { + r := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + r.SetHTMLTemplate(html) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Redirects + +Issuing a HTTP redirect is easy: + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(301, "http://www.google.com/") +}) +``` +Both internal and external locations are supported. + + +#### Custom Middlewares + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Using BasicAuth() middleware +```go +// similate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was setted by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(200, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(200, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + + +#### Goroutines inside a middleware +When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + c_cp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note than you are using the copied context "c_cp", IMPORTANT + log.Println("Done! in path " + c_cp.Request.URL.Path) + }() + }) + + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +#### Custom HTTP configuration + +Use `http.ListenAndServe()` directly, like this: + +```go +func main() { + router := gin.Default() + http.ListenAndServe(":8080", router) +} +``` +or + +```go +func main() { + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} +``` diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth.go new file mode 100644 index 00000000..7602d726 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth.go @@ -0,0 +1,94 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "crypto/subtle" + "encoding/base64" + "errors" + "sort" +) + +const ( + AuthUserKey = "user" +) + +type ( + Accounts map[string]string + authPair struct { + Value string + User string + } + authPairs []authPair +) + +func (a authPairs) Len() int { return len(a) } +func (a authPairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a authPairs) Less(i, j int) bool { return a[i].Value < a[j].Value } + +// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where +// the key is the user name and the value is the password. +func BasicAuth(accounts Accounts) HandlerFunc { + pairs, err := processAccounts(accounts) + if err != nil { + panic(err) + } + return func(c *Context) { + // Search user in the slice of allowed credentials + user, ok := searchCredential(pairs, c.Request.Header.Get("Authorization")) + if !ok { + // Credentials doesn't match, we return 401 Unauthorized and abort request. + c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"") + c.Fail(401, errors.New("Unauthorized")) + } else { + // user is allowed, set UserId to key "user" in this context, the userId can be read later using + // c.Get(gin.AuthUserKey) + c.Set(AuthUserKey, user) + } + } +} + +func processAccounts(accounts Accounts) (authPairs, error) { + if len(accounts) == 0 { + return nil, errors.New("Empty list of authorized credentials") + } + pairs := make(authPairs, 0, len(accounts)) + for user, password := range accounts { + if len(user) == 0 { + return nil, errors.New("User can not be empty") + } + base := user + ":" + password + value := "Basic " + base64.StdEncoding.EncodeToString([]byte(base)) + pairs = append(pairs, authPair{ + Value: value, + User: user, + }) + } + // We have to sort the credentials in order to use bsearch later. + sort.Sort(pairs) + return pairs, nil +} + +func searchCredential(pairs authPairs, auth string) (string, bool) { + if len(auth) == 0 { + return "", false + } + // Search user in the slice of allowed credentials + r := sort.Search(len(pairs), func(i int) bool { return pairs[i].Value >= auth }) + if r < len(pairs) && secureCompare(pairs[r].Value, auth) { + return pairs[r].User, true + } else { + return "", false + } +} + +func secureCompare(given, actual string) bool { + if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { + return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 + } else { + /* Securely compare actual to itself to keep constant time, but always return false */ + return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth_test.go new file mode 100644 index 00000000..d60c587b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/auth_test.go @@ -0,0 +1,61 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "encoding/base64" + "net/http" + "net/http/httptest" + "testing" +) + +func TestBasicAuthSucceed(t *testing.T) { + req, _ := http.NewRequest("GET", "/login", nil) + w := httptest.NewRecorder() + + r := New() + accounts := Accounts{"admin": "password"} + r.Use(BasicAuth(accounts)) + + r.GET("/login", func(c *Context) { + c.String(200, "autorized") + }) + + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Errorf("Response code should be Ok, was: %s", w.Code) + } + bodyAsString := w.Body.String() + + if bodyAsString != "autorized" { + t.Errorf("Response body should be `autorized`, was %s", bodyAsString) + } +} + +func TestBasicAuth401(t *testing.T) { + req, _ := http.NewRequest("GET", "/login", nil) + w := httptest.NewRecorder() + + r := New() + accounts := Accounts{"foo": "bar"} + r.Use(BasicAuth(accounts)) + + r.GET("/login", func(c *Context) { + c.String(200, "autorized") + }) + + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + r.ServeHTTP(w, req) + + if w.Code != 401 { + t.Errorf("Response code should be Not autorized, was: %s", w.Code) + } + + if w.HeaderMap.Get("WWW-Authenticate") != "Basic realm=\"Authorization Required\"" { + t.Errorf("WWW-Authenticate header is incorrect: %s", w.HeaderMap.Get("Content-Type")) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/binding/binding.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/binding/binding.go new file mode 100644 index 00000000..99f3d0e1 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/binding/binding.go @@ -0,0 +1,216 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "encoding/json" + "encoding/xml" + "errors" + "net/http" + "reflect" + "strconv" + "strings" +) + +type ( + Binding interface { + Bind(*http.Request, interface{}) error + } + + // JSON binding + jsonBinding struct{} + + // XML binding + xmlBinding struct{} + + // // form binding + formBinding struct{} +) + +var ( + JSON = jsonBinding{} + XML = xmlBinding{} + Form = formBinding{} // todo +) + +func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error { + decoder := json.NewDecoder(req.Body) + if err := decoder.Decode(obj); err == nil { + return Validate(obj) + } else { + return err + } +} + +func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error { + decoder := xml.NewDecoder(req.Body) + if err := decoder.Decode(obj); err == nil { + return Validate(obj) + } else { + return err + } +} + +func (_ formBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := mapForm(obj, req.Form); err != nil { + return err + } + return Validate(obj) +} + +func mapForm(ptr interface{}, form map[string][]string) error { + typ := reflect.TypeOf(ptr).Elem() + formStruct := reflect.ValueOf(ptr).Elem() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" { + structField := formStruct.Field(i) + if !structField.CanSet() { + continue + } + + inputValue, exists := form[inputFieldName] + if !exists { + continue + } + numElems := len(inputValue) + if structField.Kind() == reflect.Slice && numElems > 0 { + sliceOf := structField.Type().Elem().Kind() + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for i := 0; i < numElems; i++ { + if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil { + return err + } + } + formStruct.Field(i).Set(slice) + } else { + if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err + } + } + } + } + return nil +} + +func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { + switch valueKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val == "" { + val = "0" + } + intVal, err := strconv.Atoi(val) + if err != nil { + return err + } else { + structField.SetInt(int64(intVal)) + } + case reflect.Bool: + if val == "" { + val = "false" + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return err + } else { + structField.SetBool(boolVal) + } + case reflect.Float32: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 32) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.Float64: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.String: + structField.SetString(val) + } + return nil +} + +// Don't pass in pointers to bind to. Can lead to bugs. See: +// https://github.com/codegangsta/martini-contrib/issues/40 +// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659 +func ensureNotPointer(obj interface{}) { + if reflect.TypeOf(obj).Kind() == reflect.Ptr { + panic("Pointers are not accepted as binding models") + } +} + +func Validate(obj interface{}, parents ...string) error { + typ := reflect.TypeOf(obj) + val := reflect.ValueOf(obj) + + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } + + switch typ.Kind() { + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + + // Allow ignored and unexported fields in the struct + if len(field.PkgPath) > 0 || field.Tag.Get("form") == "-" { + continue + } + + fieldValue := val.Field(i).Interface() + zero := reflect.Zero(field.Type).Interface() + + if strings.Index(field.Tag.Get("binding"), "required") > -1 { + fieldType := field.Type.Kind() + if fieldType == reflect.Struct { + if reflect.DeepEqual(zero, fieldValue) { + return errors.New("Required " + field.Name) + } + err := Validate(fieldValue, field.Name) + if err != nil { + return err + } + } else if reflect.DeepEqual(zero, fieldValue) { + if len(parents) > 0 { + return errors.New("Required " + field.Name + " on " + parents[0]) + } else { + return errors.New("Required " + field.Name) + } + } else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct { + err := Validate(fieldValue) + if err != nil { + return err + } + } + } + } + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + fieldValue := val.Index(i).Interface() + err := Validate(fieldValue) + if err != nil { + return err + } + } + default: + return nil + } + return nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context.go new file mode 100644 index 00000000..2f0e2d8c --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context.go @@ -0,0 +1,434 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "errors" + "fmt" + "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/render" + "github.com/julienschmidt/httprouter" + "log" + "net" + "net/http" + "strings" +) + +const ( + ErrorTypeInternal = 1 << iota + ErrorTypeExternal = 1 << iota + ErrorTypeAll = 0xffffffff +) + +// Used internally to collect errors that occurred during an http request. +type errorMsg struct { + Err string `json:"error"` + Type uint32 `json:"-"` + Meta interface{} `json:"meta"` +} + +type errorMsgs []errorMsg + +func (a errorMsgs) ByType(typ uint32) errorMsgs { + if len(a) == 0 { + return a + } + result := make(errorMsgs, 0, len(a)) + for _, msg := range a { + if msg.Type&typ > 0 { + result = append(result, msg) + } + } + return result +} + +func (a errorMsgs) String() string { + if len(a) == 0 { + return "" + } + var buffer bytes.Buffer + for i, msg := range a { + text := fmt.Sprintf("Error #%02d: %s \n Meta: %v\n", (i + 1), msg.Err, msg.Meta) + buffer.WriteString(text) + } + return buffer.String() +} + +// Context is the most important part of gin. It allows us to pass variables between middleware, +// manage the flow, validate the JSON of a request and render a JSON response for example. +type Context struct { + writermem responseWriter + Request *http.Request + Writer ResponseWriter + Keys map[string]interface{} + Errors errorMsgs + Params httprouter.Params + Engine *Engine + handlers []HandlerFunc + index int8 + accepted []string +} + +/************************************/ +/********** CONTEXT CREATION ********/ +/************************************/ + +func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { + c := engine.pool.Get().(*Context) + c.writermem.reset(w) + c.Request = req + c.Params = params + c.handlers = handlers + c.Keys = nil + c.index = -1 + c.accepted = nil + c.Errors = c.Errors[0:0] + return c +} + +func (engine *Engine) reuseContext(c *Context) { + engine.pool.Put(c) +} + +func (c *Context) Copy() *Context { + var cp Context = *c + cp.index = AbortIndex + cp.handlers = nil + return &cp +} + +/************************************/ +/*************** FLOW ***************/ +/************************************/ + +// Next should be used only in the middlewares. +// It executes the pending handlers in the chain inside the calling handler. +// See example in github. +func (c *Context) Next() { + c.index++ + s := int8(len(c.handlers)) + for ; c.index < s; c.index++ { + c.handlers[c.index](c) + } +} + +// Forces the system to do not continue calling the pending handlers in the chain. +func (c *Context) Abort() { + c.index = AbortIndex +} + +// Same than AbortWithStatus() but also writes the specified response status code. +// For example, the first handler checks if the request is authorized. If it's not, context.AbortWithStatus(401) should be called. +func (c *Context) AbortWithStatus(code int) { + c.Writer.WriteHeader(code) + c.Abort() +} + +/************************************/ +/********* ERROR MANAGEMENT *********/ +/************************************/ + +// Fail is the same as Abort plus an error message. +// Calling `context.Fail(500, err)` is equivalent to: +// ``` +// context.Error("Operation aborted", err) +// context.AbortWithStatus(500) +// ``` +func (c *Context) Fail(code int, err error) { + c.Error(err, "Operation aborted") + c.AbortWithStatus(code) +} + +func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) { + c.Errors = append(c.Errors, errorMsg{ + Err: err.Error(), + Type: typ, + Meta: meta, + }) +} + +// Attaches an error to the current context. The error is pushed to a list of errors. +// It's a good idea to call Error for each error that occurred during the resolution of a request. +// A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. +func (c *Context) Error(err error, meta interface{}) { + c.ErrorTyped(err, ErrorTypeExternal, meta) +} + +func (c *Context) LastError() error { + nuErrors := len(c.Errors) + if nuErrors > 0 { + return errors.New(c.Errors[nuErrors-1].Err) + } else { + return nil + } +} + +/************************************/ +/******** METADATA MANAGEMENT********/ +/************************************/ + +// Sets a new pair key/value just for the specified context. +// It also lazy initializes the hashmap. +func (c *Context) Set(key string, item interface{}) { + if c.Keys == nil { + c.Keys = make(map[string]interface{}) + } + c.Keys[key] = item +} + +// Get returns the value for the given key or an error if the key does not exist. +func (c *Context) Get(key string) (interface{}, error) { + if c.Keys != nil { + value, ok := c.Keys[key] + if ok { + return value, nil + } + } + return nil, errors.New("Key does not exist.") +} + +// MustGet returns the value for the given key or panics if the value doesn't exist. +func (c *Context) MustGet(key string) interface{} { + value, err := c.Get(key) + if err != nil || value == nil { + log.Panicf("Key %s doesn't exist", value) + } + return value +} + +func ipInMasks(ip net.IP, masks []interface{}) bool { + for _, proxy := range masks { + var mask *net.IPNet + var err error + + switch t := proxy.(type) { + case string: + if _, mask, err = net.ParseCIDR(t); err != nil { + panic(err) + } + case net.IP: + mask = &net.IPNet{IP: t, Mask: net.CIDRMask(len(t)*8, len(t)*8)} + case net.IPNet: + mask = &t + } + + if mask.Contains(ip) { + return true + } + } + + return false +} + +// the ForwardedFor middleware unwraps the X-Forwarded-For headers, be careful to only use this +// middleware if you've got servers in front of this server. The list with (known) proxies and +// local ips are being filtered out of the forwarded for list, giving the last not local ip being +// the real client ip. +func ForwardedFor(proxies ...interface{}) HandlerFunc { + if len(proxies) == 0 { + // default to local ips + var reservedLocalIps = []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} + + proxies = make([]interface{}, len(reservedLocalIps)) + + for i, v := range reservedLocalIps { + proxies[i] = v + } + } + + return func(c *Context) { + // the X-Forwarded-For header contains an array with left most the client ip, then + // comma separated, all proxies the request passed. The last proxy appears + // as the remote address of the request. Returning the client + // ip to comply with default RemoteAddr response. + + // check if remoteaddr is local ip or in list of defined proxies + remoteIp := net.ParseIP(strings.Split(c.Request.RemoteAddr, ":")[0]) + + if !ipInMasks(remoteIp, proxies) { + return + } + + if forwardedFor := c.Request.Header.Get("X-Forwarded-For"); forwardedFor != "" { + parts := strings.Split(forwardedFor, ",") + + for i := len(parts) - 1; i >= 0; i-- { + part := parts[i] + + ip := net.ParseIP(strings.TrimSpace(part)) + + if ipInMasks(ip, proxies) { + continue + } + + // returning remote addr conform the original remote addr format + c.Request.RemoteAddr = ip.String() + ":0" + + // remove forwarded for address + c.Request.Header.Set("X-Forwarded-For", "") + return + } + } + } +} + +func (c *Context) ClientIP() string { + return c.Request.RemoteAddr +} + +/************************************/ +/********* PARSING REQUEST **********/ +/************************************/ + +// This function checks the Content-Type to select a binding engine automatically, +// Depending the "Content-Type" header different bindings are used: +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// else --> returns an error +// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. +func (c *Context) Bind(obj interface{}) bool { + var b binding.Binding + ctype := filterFlags(c.Request.Header.Get("Content-Type")) + switch { + case c.Request.Method == "GET" || ctype == MIMEPOSTForm: + b = binding.Form + case ctype == MIMEJSON: + b = binding.JSON + case ctype == MIMEXML || ctype == MIMEXML2: + b = binding.XML + default: + c.Fail(400, errors.New("unknown content-type: "+ctype)) + return false + } + return c.BindWith(obj, b) +} + +func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { + if err := b.Bind(c.Request, obj); err != nil { + c.Fail(400, err) + return false + } + return true +} + +/************************************/ +/******** RESPONSE RENDERING ********/ +/************************************/ + +func (c *Context) Render(code int, render render.Render, obj ...interface{}) { + if err := render.Render(c.Writer, code, obj...); err != nil { + c.ErrorTyped(err, ErrorTypeInternal, obj) + c.AbortWithStatus(500) + } +} + +// Serializes the given struct as JSON into the response body in a fast and efficient way. +// It also sets the Content-Type as "application/json". +func (c *Context) JSON(code int, obj interface{}) { + c.Render(code, render.JSON, obj) +} + +// Serializes the given struct as XML into the response body in a fast and efficient way. +// It also sets the Content-Type as "application/xml". +func (c *Context) XML(code int, obj interface{}) { + c.Render(code, render.XML, obj) +} + +// Renders the HTTP template specified by its file name. +// It also updates the HTTP code and sets the Content-Type as "text/html". +// See http://golang.org/doc/articles/wiki/ +func (c *Context) HTML(code int, name string, obj interface{}) { + c.Render(code, c.Engine.HTMLRender, name, obj) +} + +// Writes the given string into the response body and sets the Content-Type to "text/plain". +func (c *Context) String(code int, format string, values ...interface{}) { + c.Render(code, render.Plain, format, values) +} + +// Returns a HTTP redirect to the specific location. +func (c *Context) Redirect(code int, location string) { + if code >= 300 && code <= 308 { + c.Render(code, render.Redirect, location) + } else { + panic(fmt.Sprintf("Cannot send a redirect with status code %d", code)) + } +} + +// Writes some data into the body stream and updates the HTTP code. +func (c *Context) Data(code int, contentType string, data []byte) { + if len(contentType) > 0 { + c.Writer.Header().Set("Content-Type", contentType) + } + c.Writer.WriteHeader(code) + c.Writer.Write(data) +} + +// Writes the specified file into the body stream +func (c *Context) File(filepath string) { + http.ServeFile(c.Writer, c.Request, filepath) +} + +/************************************/ +/******** CONTENT NEGOTIATION *******/ +/************************************/ + +type Negotiate struct { + Offered []string + HTMLPath string + HTMLData interface{} + JSONData interface{} + XMLData interface{} + Data interface{} +} + +func (c *Context) Negotiate(code int, config Negotiate) { + switch c.NegotiateFormat(config.Offered...) { + case MIMEJSON: + data := chooseData(config.JSONData, config.Data) + c.JSON(code, data) + + case MIMEHTML: + data := chooseData(config.HTMLData, config.Data) + if len(config.HTMLPath) == 0 { + panic("negotiate config is wrong. html path is needed") + } + c.HTML(code, config.HTMLPath, data) + + case MIMEXML: + data := chooseData(config.XMLData, config.Data) + c.XML(code, data) + + default: + c.Fail(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) + } +} + +func (c *Context) NegotiateFormat(offered ...string) string { + if len(offered) == 0 { + panic("you must provide at least one offer") + } + if c.accepted == nil { + c.accepted = parseAccept(c.Request.Header.Get("Accept")) + } + if len(c.accepted) == 0 { + return offered[0] + + } else { + for _, accepted := range c.accepted { + for _, offert := range offered { + if accepted == offert { + return offert + } + } + } + return "" + } +} + +func (c *Context) SetAccepted(formats ...string) { + c.accepted = formats +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context_test.go new file mode 100644 index 00000000..c77cd279 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/context_test.go @@ -0,0 +1,483 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "errors" + "html/template" + "net/http" + "net/http/httptest" + "testing" +) + +// TestContextParamsGet tests that a parameter can be parsed from the URL. +func TestContextParamsByName(t *testing.T) { + req, _ := http.NewRequest("GET", "/test/alexandernyquist", nil) + w := httptest.NewRecorder() + name := "" + + r := New() + r.GET("/test/:name", func(c *Context) { + name = c.Params.ByName("name") + }) + + r.ServeHTTP(w, req) + + if name != "alexandernyquist" { + t.Errorf("Url parameter was not correctly parsed. Should be alexandernyquist, was %s.", name) + } +} + +// TestContextSetGet tests that a parameter is set correctly on the +// current context and can be retrieved using Get. +func TestContextSetGet(t *testing.T) { + req, _ := http.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test", func(c *Context) { + // Key should be lazily created + if c.Keys != nil { + t.Error("Keys should be nil") + } + + // Set + c.Set("foo", "bar") + + v, err := c.Get("foo") + if err != nil { + t.Errorf("Error on exist key") + } + if v != "bar" { + t.Errorf("Value should be bar, was %s", v) + } + }) + + r.ServeHTTP(w, req) +} + +// TestContextJSON tests that the response is serialized as JSON +// and Content-Type is set to application/json +func TestContextJSON(t *testing.T) { + req, _ := http.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test", func(c *Context) { + c.JSON(200, H{"foo": "bar"}) + }) + + r.ServeHTTP(w, req) + + if w.Body.String() != "{\"foo\":\"bar\"}\n" { + t.Errorf("Response should be {\"foo\":\"bar\"}, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { + t.Errorf("Content-Type should be application/json, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestContextHTML tests that the response executes the templates +// and responds with Content-Type set to text/html +func TestContextHTML(t *testing.T) { + req, _ := http.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + + r := New() + templ, _ := template.New("t").Parse(`Hello {{.Name}}`) + r.SetHTMLTemplate(templ) + + type TestData struct{ Name string } + + r.GET("/test", func(c *Context) { + c.HTML(200, "t", TestData{"alexandernyquist"}) + }) + + r.ServeHTTP(w, req) + + if w.Body.String() != "Hello alexandernyquist" { + t.Errorf("Response should be Hello alexandernyquist, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "text/html; charset=utf-8" { + t.Errorf("Content-Type should be text/html, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestContextString tests that the response is returned +// with Content-Type set to text/plain +func TestContextString(t *testing.T) { + req, _ := http.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test", func(c *Context) { + c.String(200, "test") + }) + + r.ServeHTTP(w, req) + + if w.Body.String() != "test" { + t.Errorf("Response should be test, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { + t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestContextXML tests that the response is serialized as XML +// and Content-Type is set to application/xml +func TestContextXML(t *testing.T) { + req, _ := http.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test", func(c *Context) { + c.XML(200, H{"foo": "bar"}) + }) + + r.ServeHTTP(w, req) + + if w.Body.String() != "bar" { + t.Errorf("Response should be bar, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "application/xml; charset=utf-8" { + t.Errorf("Content-Type should be application/xml, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestContextData tests that the response can be written from `bytesting` +// with specified MIME type +func TestContextData(t *testing.T) { + req, _ := http.NewRequest("GET", "/test/csv", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test/csv", func(c *Context) { + c.Data(200, "text/csv", []byte(`foo,bar`)) + }) + + r.ServeHTTP(w, req) + + if w.Body.String() != "foo,bar" { + t.Errorf("Response should be foo&bar, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "text/csv" { + t.Errorf("Content-Type should be text/csv, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +func TestContextFile(t *testing.T) { + req, _ := http.NewRequest("GET", "/test/file", nil) + w := httptest.NewRecorder() + + r := New() + r.GET("/test/file", func(c *Context) { + c.File("./gin.go") + }) + + r.ServeHTTP(w, req) + + bodyAsString := w.Body.String() + + if len(bodyAsString) == 0 { + t.Errorf("Got empty body instead of file data") + } + + if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { + t.Errorf("Content-Type should be text/plain; charset=utf-8, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestHandlerFunc - ensure that custom middleware works properly +func TestHandlerFunc(t *testing.T) { + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r := New() + var stepsPassed int = 0 + + r.Use(func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }) + + r.ServeHTTP(w, req) + + if w.Code != 404 { + t.Errorf("Response code should be Not found, was: %s", w.Code) + } + + if stepsPassed != 2 { + t.Errorf("Falied to switch context in handler function: %s", stepsPassed) + } +} + +// TestBadAbortHandlersChain - ensure that Abort after switch context will not interrupt pending handlers +func TestBadAbortHandlersChain(t *testing.T) { + // SETUP + var stepsPassed int = 0 + r := New() + r.Use(func(c *Context) { + stepsPassed += 1 + c.Next() + stepsPassed += 1 + // after check and abort + c.AbortWithStatus(409) + }) + r.Use(func(c *Context) { + stepsPassed += 1 + c.Next() + stepsPassed += 1 + c.AbortWithStatus(403) + }) + + // RUN + w := PerformRequest(r, "GET", "/") + + // TEST + if w.Code != 409 { + t.Errorf("Response code should be Forbiden, was: %d", w.Code) + } + if stepsPassed != 4 { + t.Errorf("Falied to switch context in handler function: %d", stepsPassed) + } +} + +// TestAbortHandlersChain - ensure that Abort interrupt used middlewares in fifo order +func TestAbortHandlersChain(t *testing.T) { + // SETUP + var stepsPassed int = 0 + r := New() + r.Use(func(context *Context) { + stepsPassed += 1 + context.AbortWithStatus(409) + }) + r.Use(func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }) + + // RUN + w := PerformRequest(r, "GET", "/") + + // TEST + if w.Code != 409 { + t.Errorf("Response code should be Conflict, was: %d", w.Code) + } + if stepsPassed != 1 { + t.Errorf("Falied to switch context in handler function: %d", stepsPassed) + } +} + +// TestFailHandlersChain - ensure that Fail interrupt used middlewares in fifo order as +// as well as Abort +func TestFailHandlersChain(t *testing.T) { + // SETUP + var stepsPassed int = 0 + r := New() + r.Use(func(context *Context) { + stepsPassed += 1 + context.Fail(500, errors.New("foo")) + }) + r.Use(func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }) + + // RUN + w := PerformRequest(r, "GET", "/") + + // TEST + if w.Code != 500 { + t.Errorf("Response code should be Server error, was: %d", w.Code) + } + if stepsPassed != 1 { + t.Errorf("Falied to switch context in handler function: %d", stepsPassed) + } +} + +func TestBindingJSON(t *testing.T) { + + body := bytes.NewBuffer([]byte("{\"foo\":\"bar\"}")) + + r := New() + r.POST("/binding/json", func(c *Context) { + var body struct { + Foo string `json:"foo"` + } + if c.Bind(&body) { + c.JSON(200, H{"parsed": body.Foo}) + } + }) + + req, _ := http.NewRequest("POST", "/binding/json", body) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Errorf("Response code should be Ok, was: %s", w.Code) + } + + if w.Body.String() != "{\"parsed\":\"bar\"}\n" { + t.Errorf("Response should be {\"parsed\":\"bar\"}, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { + t.Errorf("Content-Type should be application/json, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +func TestBindingJSONEncoding(t *testing.T) { + + body := bytes.NewBuffer([]byte("{\"foo\":\"嘉\"}")) + + r := New() + r.POST("/binding/json", func(c *Context) { + var body struct { + Foo string `json:"foo"` + } + if c.Bind(&body) { + c.JSON(200, H{"parsed": body.Foo}) + } + }) + + req, _ := http.NewRequest("POST", "/binding/json", body) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Errorf("Response code should be Ok, was: %s", w.Code) + } + + if w.Body.String() != "{\"parsed\":\"嘉\"}\n" { + t.Errorf("Response should be {\"parsed\":\"嘉\"}, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { + t.Errorf("Content-Type should be application/json, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +func TestBindingJSONNoContentType(t *testing.T) { + + body := bytes.NewBuffer([]byte("{\"foo\":\"bar\"}")) + + r := New() + r.POST("/binding/json", func(c *Context) { + var body struct { + Foo string `json:"foo"` + } + if c.Bind(&body) { + c.JSON(200, H{"parsed": body.Foo}) + } + + }) + + req, _ := http.NewRequest("POST", "/binding/json", body) + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + if w.Code != 400 { + t.Errorf("Response code should be Bad request, was: %s", w.Code) + } + + if w.Body.String() == "{\"parsed\":\"bar\"}\n" { + t.Errorf("Response should not be {\"parsed\":\"bar\"}, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") == "application/json" { + t.Errorf("Content-Type should not be application/json, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +func TestBindingJSONMalformed(t *testing.T) { + + body := bytes.NewBuffer([]byte("\"foo\":\"bar\"\n")) + + r := New() + r.POST("/binding/json", func(c *Context) { + var body struct { + Foo string `json:"foo"` + } + if c.Bind(&body) { + c.JSON(200, H{"parsed": body.Foo}) + } + + }) + + req, _ := http.NewRequest("POST", "/binding/json", body) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + if w.Code != 400 { + t.Errorf("Response code should be Bad request, was: %s", w.Code) + } + if w.Body.String() == "{\"parsed\":\"bar\"}\n" { + t.Errorf("Response should not be {\"parsed\":\"bar\"}, was: %s", w.Body.String()) + } + + if w.HeaderMap.Get("Content-Type") == "application/json" { + t.Errorf("Content-Type should not be application/json, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +func TestClientIP(t *testing.T) { + r := New() + + var clientIP string = "" + r.GET("/", func(c *Context) { + clientIP = c.ClientIP() + }) + + body := bytes.NewBuffer([]byte("")) + req, _ := http.NewRequest("GET", "/", body) + req.RemoteAddr = "clientip:1234" + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + if clientIP != "clientip:1234" { + t.Errorf("ClientIP should not be %s, but clientip:1234", clientIP) + } +} + +func TestClientIPWithXForwardedForWithProxy(t *testing.T) { + r := New() + r.Use(ForwardedFor()) + + var clientIP string = "" + r.GET("/", func(c *Context) { + clientIP = c.ClientIP() + }) + + body := bytes.NewBuffer([]byte("")) + req, _ := http.NewRequest("GET", "/", body) + req.RemoteAddr = "172.16.8.3:1234" + req.Header.Set("X-Real-Ip", "realip") + req.Header.Set("X-Forwarded-For", "1.2.3.4, 10.10.0.4, 192.168.0.43, 172.16.8.4") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + if clientIP != "1.2.3.4:0" { + t.Errorf("ClientIP should not be %s, but 1.2.3.4:0", clientIP) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/deprecated.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/deprecated.go new file mode 100644 index 00000000..71881530 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/deprecated.go @@ -0,0 +1,47 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "github.com/gin-gonic/gin/binding" + "net/http" +) + +// DEPRECATED, use Bind() instead. +// Like ParseBody() but this method also writes a 400 error if the json is not valid. +func (c *Context) EnsureBody(item interface{}) bool { + return c.Bind(item) +} + +// DEPRECATED use bindings directly +// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. +func (c *Context) ParseBody(item interface{}) error { + return binding.JSON.Bind(c.Request, item) +} + +// DEPRECATED use gin.Static() instead +// ServeFiles serves files from the given file system root. +// The path must end with "/*filepath", files are then served from the local +// path /defined/root/dir/*filepath. +// For example if root is "/etc" and *filepath is "passwd", the local file +// "/etc/passwd" would be served. +// Internally a http.FileServer is used, therefore http.NotFound is used instead +// of the Router's NotFound handler. +// To use the operating system's file system implementation, +// use http.Dir: +// router.ServeFiles("/src/*filepath", http.Dir("/var/www")) +func (engine *Engine) ServeFiles(path string, root http.FileSystem) { + engine.router.ServeFiles(path, root) +} + +// DEPRECATED use gin.LoadHTMLGlob() or gin.LoadHTMLFiles() instead +func (engine *Engine) LoadHTMLTemplates(pattern string) { + engine.LoadHTMLGlob(pattern) +} + +// DEPRECATED. Use NoRoute() instead +func (engine *Engine) NotFound404(handlers ...HandlerFunc) { + engine.NoRoute(handlers...) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/README.md new file mode 100644 index 00000000..48505de8 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/README.md @@ -0,0 +1,7 @@ +# Guide to run Gin under App Engine LOCAL Development Server + +1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) +2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go` +3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` +4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/` +5. Run it: `$ goapp serve app-engine/` \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/app.yaml b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/app.yaml new file mode 100644 index 00000000..5f20cf3f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/app.yaml @@ -0,0 +1,8 @@ +application: hello +version: 1 +runtime: go +api_version: go1 + +handlers: +- url: /.* + script: _go_app \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/hello.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/hello.go new file mode 100644 index 00000000..f5daf824 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/app-engine/hello.go @@ -0,0 +1,23 @@ +package hello + +import ( + "net/http" + "github.com/gin-gonic/gin" +) + +// This function's name is a must. App Engine uses it to drive the requests properly. +func init() { + // Starts a new Gin instance with no middle-ware + r := gin.New() + + // Define your handlers + r.GET("/", func(c *gin.Context){ + c.String(200, "Hello World!") + }) + r.GET("/ping", func(c *gin.Context){ + c.String(200, "pong") + }) + + // Handle all requests using net/http + http.Handle("/", r) +} \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/example_basic.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/example_basic.go new file mode 100644 index 00000000..919580d8 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/example_basic.go @@ -0,0 +1,56 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +var DB = make(map[string]string) + +func main() { + r := gin.Default() + + // Ping test + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + // Get user value + r.GET("/user/:name", func(c *gin.Context) { + user := c.Params.ByName("name") + value, ok := DB[user] + if ok { + c.JSON(200, gin.H{"user": user, "value": value}) + } else { + c.JSON(200, gin.H{"user": user, "status": "no value"}) + } + }) + + // Authorized group (uses gin.BasicAuth() middleware) + // Same than: + // authorized := r.Group("/") + // authorized.Use(gin.BasicAuth(gin.Credentials{ + // "foo": "bar", + // "manu": "123", + //})) + authorized := r.Group("/", gin.BasicAuth(gin.Accounts{ + "foo": "bar", // user:foo password:bar + "manu": "123", // user:manu password:123 + })) + + authorized.POST("admin", func(c *gin.Context) { + user := c.MustGet(gin.AuthUserKey).(string) + + // Parse JSON + var json struct { + Value string `json:"value" binding:"required"` + } + + if c.Bind(&json) { + DB[user] = json.Value + c.JSON(200, gin.H{"status": "ok"}) + } + }) + + // Listen and Server in 0.0.0.0:8080 + r.Run(":8080") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/example_pongo2.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/example_pongo2.go new file mode 100644 index 00000000..9f745e1e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/example_pongo2.go @@ -0,0 +1,58 @@ +package main + +import ( + "github.com/flosch/pongo2" + "github.com/gin-gonic/gin" + "net/http" +) + +type pongoRender struct { + cache map[string]*pongo2.Template +} + +func newPongoRender() *pongoRender { + return &pongoRender{map[string]*pongo2.Template{}} +} + +func writeHeader(w http.ResponseWriter, code int, contentType string) { + if code >= 0 { + w.Header().Set("Content-Type", contentType) + w.WriteHeader(code) + } +} + +func (p *pongoRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + file := data[0].(string) + ctx := data[1].(pongo2.Context) + var t *pongo2.Template + + if tmpl, ok := p.cache[file]; ok { + t = tmpl + } else { + tmpl, err := pongo2.FromFile(file) + if err != nil { + return err + } + p.cache[file] = tmpl + t = tmpl + } + writeHeader(w, code, "text/html") + return t.ExecuteWriter(ctx, w) +} + +func main() { + r := gin.Default() + r.HTMLRender = newPongoRender() + + r.GET("/index", func(c *gin.Context) { + name := c.Request.FormValue("name") + ctx := pongo2.Context{ + "title": "Gin meets pongo2 !", + "name": name, + } + c.HTML(200, "index.html", ctx) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/index.html b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/index.html new file mode 100644 index 00000000..8b293edf --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/examples/pluggable_renderer/index.html @@ -0,0 +1,12 @@ + + + + + {{ title }} + + + + + Hello {{ name }} ! + + diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin.go new file mode 100644 index 00000000..42c4b1f6 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin.go @@ -0,0 +1,143 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "github.com/gin-gonic/gin/render" + "github.com/julienschmidt/httprouter" + "html/template" + "math" + "net/http" + "sync" +) + +const ( + AbortIndex = math.MaxInt8 / 2 + MIMEJSON = "application/json" + MIMEHTML = "text/html" + MIMEXML = "application/xml" + MIMEXML2 = "text/xml" + MIMEPlain = "text/plain" + MIMEPOSTForm = "application/x-www-form-urlencoded" +) + +type ( + HandlerFunc func(*Context) + + // Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares. + Engine struct { + *RouterGroup + HTMLRender render.Render + Default404Body []byte + pool sync.Pool + allNoRoute []HandlerFunc + noRoute []HandlerFunc + router *httprouter.Router + } +) + +// Returns a new blank Engine instance without any middleware attached. +// The most basic configuration +func New() *Engine { + engine := &Engine{} + engine.RouterGroup = &RouterGroup{ + Handlers: nil, + absolutePath: "/", + engine: engine, + } + engine.router = httprouter.New() + engine.Default404Body = []byte("404 page not found") + engine.router.NotFound = engine.handle404 + engine.pool.New = func() interface{} { + c := &Context{Engine: engine} + c.Writer = &c.writermem + return c + } + return engine +} + +// Returns a Engine instance with the Logger and Recovery already attached. +func Default() *Engine { + engine := New() + engine.Use(Recovery(), Logger()) + return engine +} + +func (engine *Engine) LoadHTMLGlob(pattern string) { + if IsDebugging() { + render.HTMLDebug.AddGlob(pattern) + engine.HTMLRender = render.HTMLDebug + } else { + templ := template.Must(template.ParseGlob(pattern)) + engine.SetHTMLTemplate(templ) + } +} + +func (engine *Engine) LoadHTMLFiles(files ...string) { + if IsDebugging() { + render.HTMLDebug.AddFiles(files...) + engine.HTMLRender = render.HTMLDebug + } else { + templ := template.Must(template.ParseFiles(files...)) + engine.SetHTMLTemplate(templ) + } +} + +func (engine *Engine) SetHTMLTemplate(templ *template.Template) { + engine.HTMLRender = render.HTMLRender{ + Template: templ, + } +} + +// Adds handlers for NoRoute. It return a 404 code by default. +func (engine *Engine) NoRoute(handlers ...HandlerFunc) { + engine.noRoute = handlers + engine.rebuild404Handlers() +} + +func (engine *Engine) Use(middlewares ...HandlerFunc) { + engine.RouterGroup.Use(middlewares...) + engine.rebuild404Handlers() +} + +func (engine *Engine) rebuild404Handlers() { + engine.allNoRoute = engine.combineHandlers(engine.noRoute) +} + +func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { + c := engine.createContext(w, req, nil, engine.allNoRoute) + // set 404 by default, useful for logging + c.Writer.WriteHeader(404) + c.Next() + if !c.Writer.Written() { + if c.Writer.Status() == 404 { + c.Data(-1, MIMEPlain, engine.Default404Body) + } else { + c.Writer.WriteHeaderNow() + } + } + engine.reuseContext(c) +} + +// ServeHTTP makes the router implement the http.Handler interface. +func (engine *Engine) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + engine.router.ServeHTTP(writer, request) +} + +func (engine *Engine) Run(addr string) error { + debugPrint("Listening and serving HTTP on %s\n", addr) + if err := http.ListenAndServe(addr, engine); err != nil { + return err + } + return nil +} + +func (engine *Engine) RunTLS(addr string, cert string, key string) error { + debugPrint("Listening and serving HTTPS on %s\n", addr) + if err := http.ListenAndServeTLS(addr, cert, key, engine); err != nil { + return err + } + return nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin_test.go new file mode 100644 index 00000000..ba74c159 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/gin_test.go @@ -0,0 +1,206 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path" + "strings" + "testing" +) + +func init() { + SetMode(TestMode) +} + +func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { + req, _ := http.NewRequest(method, path, nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + return w +} + +// TestSingleRouteOK tests that POST route is correctly invoked. +func testRouteOK(method string, t *testing.T) { + // SETUP + passed := false + r := New() + r.Handle(method, "/test", []HandlerFunc{func(c *Context) { + passed = true + }}) + + // RUN + w := PerformRequest(r, method, "/test") + + // TEST + if passed == false { + t.Errorf(method + " route handler was not invoked.") + } + if w.Code != http.StatusOK { + t.Errorf("Status code should be %v, was %d", http.StatusOK, w.Code) + } +} +func TestRouterGroupRouteOK(t *testing.T) { + testRouteOK("POST", t) + testRouteOK("DELETE", t) + testRouteOK("PATCH", t) + testRouteOK("PUT", t) + testRouteOK("OPTIONS", t) + testRouteOK("HEAD", t) +} + +// TestSingleRouteOK tests that POST route is correctly invoked. +func testRouteNotOK(method string, t *testing.T) { + // SETUP + passed := false + r := New() + r.Handle(method, "/test_2", []HandlerFunc{func(c *Context) { + passed = true + }}) + + // RUN + w := PerformRequest(r, method, "/test") + + // TEST + if passed == true { + t.Errorf(method + " route handler was invoked, when it should not") + } + if w.Code != http.StatusNotFound { + // If this fails, it's because httprouter needs to be updated to at least f78f58a0db + t.Errorf("Status code should be %v, was %d. Location: %s", http.StatusNotFound, w.Code, w.HeaderMap.Get("Location")) + } +} + +// TestSingleRouteOK tests that POST route is correctly invoked. +func TestRouteNotOK(t *testing.T) { + testRouteNotOK("POST", t) + testRouteNotOK("DELETE", t) + testRouteNotOK("PATCH", t) + testRouteNotOK("PUT", t) + testRouteNotOK("OPTIONS", t) + testRouteNotOK("HEAD", t) +} + +// TestSingleRouteOK tests that POST route is correctly invoked. +func testRouteNotOK2(method string, t *testing.T) { + // SETUP + passed := false + r := New() + var methodRoute string + if method == "POST" { + methodRoute = "GET" + } else { + methodRoute = "POST" + } + r.Handle(methodRoute, "/test", []HandlerFunc{func(c *Context) { + passed = true + }}) + + // RUN + w := PerformRequest(r, method, "/test") + + // TEST + if passed == true { + t.Errorf(method + " route handler was invoked, when it should not") + } + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("Status code should be %v, was %d. Location: %s", http.StatusMethodNotAllowed, w.Code, w.HeaderMap.Get("Location")) + } +} + +// TestSingleRouteOK tests that POST route is correctly invoked. +func TestRouteNotOK2(t *testing.T) { + testRouteNotOK2("POST", t) + testRouteNotOK2("DELETE", t) + testRouteNotOK2("PATCH", t) + testRouteNotOK2("PUT", t) + testRouteNotOK2("OPTIONS", t) + testRouteNotOK2("HEAD", t) +} + +// TestHandleStaticFile - ensure the static file handles properly +func TestHandleStaticFile(t *testing.T) { + // SETUP file + testRoot, _ := os.Getwd() + f, err := ioutil.TempFile(testRoot, "") + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + filePath := path.Join("/", path.Base(f.Name())) + f.WriteString("Gin Web Framework") + f.Close() + + // SETUP gin + r := New() + r.Static("./", testRoot) + + // RUN + w := PerformRequest(r, "GET", filePath) + + // TEST + if w.Code != 200 { + t.Errorf("Response code should be 200, was: %d", w.Code) + } + if w.Body.String() != "Gin Web Framework" { + t.Errorf("Response should be test, was: %s", w.Body.String()) + } + if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { + t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestHandleStaticDir - ensure the root/sub dir handles properly +func TestHandleStaticDir(t *testing.T) { + // SETUP + r := New() + r.Static("/", "./") + + // RUN + w := PerformRequest(r, "GET", "/") + + // TEST + bodyAsString := w.Body.String() + if w.Code != 200 { + t.Errorf("Response code should be 200, was: %d", w.Code) + } + if len(bodyAsString) == 0 { + t.Errorf("Got empty body instead of file tree") + } + if !strings.Contains(bodyAsString, "gin.go") { + t.Errorf("Can't find:`gin.go` in file tree: %s", bodyAsString) + } + if w.HeaderMap.Get("Content-Type") != "text/html; charset=utf-8" { + t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestHandleHeadToDir - ensure the root/sub dir handles properly +func TestHandleHeadToDir(t *testing.T) { + // SETUP + r := New() + r.Static("/", "./") + + // RUN + w := PerformRequest(r, "HEAD", "/") + + // TEST + bodyAsString := w.Body.String() + if w.Code != 200 { + t.Errorf("Response code should be Ok, was: %s", w.Code) + } + if len(bodyAsString) == 0 { + t.Errorf("Got empty body instead of file tree") + } + if !strings.Contains(bodyAsString, "gin.go") { + t.Errorf("Can't find:`gin.go` in file tree: %s", bodyAsString) + } + if w.HeaderMap.Get("Content-Type") != "text/html; charset=utf-8" { + t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/logger.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/logger.go new file mode 100644 index 00000000..5054f6ec --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/logger.go @@ -0,0 +1,105 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "log" + "os" + "time" +) + +var ( + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) +) + +func ErrorLogger() HandlerFunc { + return ErrorLoggerT(ErrorTypeAll) +} + +func ErrorLoggerT(typ uint32) HandlerFunc { + return func(c *Context) { + c.Next() + + errs := c.Errors.ByType(typ) + if len(errs) > 0 { + // -1 status code = do not change current one + c.JSON(-1, c.Errors) + } + } +} + +func Logger() HandlerFunc { + stdlogger := log.New(os.Stdout, "", 0) + //errlogger := log.New(os.Stderr, "", 0) + + return func(c *Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Stop timer + end := time.Now() + latency := end.Sub(start) + + clientIP := c.ClientIP() + method := c.Request.Method + statusCode := c.Writer.Status() + statusColor := colorForStatus(statusCode) + methodColor := colorForMethod(method) + + stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s |%s %s %-7s %s\n%s", + end.Format("2006/01/02 - 15:04:05"), + statusColor, statusCode, reset, + latency, + clientIP, + methodColor, reset, method, + c.Request.URL.Path, + c.Errors.String(), + ) + } +} + +func colorForStatus(code int) string { + switch { + case code >= 200 && code <= 299: + return green + case code >= 300 && code <= 399: + return white + case code >= 400 && code <= 499: + return yellow + default: + return red + } +} + +func colorForMethod(method string) string { + switch { + case method == "GET": + return blue + case method == "POST": + return cyan + case method == "PUT": + return yellow + case method == "DELETE": + return red + case method == "PATCH": + return green + case method == "HEAD": + return magenta + case method == "OPTIONS": + return white + default: + return reset + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/mode.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/mode.go new file mode 100644 index 00000000..0495b830 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/mode.go @@ -0,0 +1,63 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "fmt" + "os" +) + +const GIN_MODE = "GIN_MODE" + +const ( + DebugMode string = "debug" + ReleaseMode string = "release" + TestMode string = "test" +) +const ( + debugCode = iota + releaseCode = iota + testCode = iota +) + +var gin_mode int = debugCode +var mode_name string = DebugMode + +func init() { + value := os.Getenv(GIN_MODE) + if len(value) == 0 { + SetMode(DebugMode) + } else { + SetMode(value) + } +} + +func SetMode(value string) { + switch value { + case DebugMode: + gin_mode = debugCode + case ReleaseMode: + gin_mode = releaseCode + case TestMode: + gin_mode = testCode + default: + panic("gin mode unknown: " + value) + } + mode_name = value +} + +func Mode() string { + return mode_name +} + +func IsDebugging() bool { + return gin_mode == debugCode +} + +func debugPrint(format string, values ...interface{}) { + if IsDebugging() { + fmt.Printf("[GIN-debug] "+format, values...) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery.go new file mode 100644 index 00000000..a8d537e4 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery.go @@ -0,0 +1,98 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "net/http" + "runtime" +) + +var ( + dunno = []byte("???") + centerDot = []byte("·") + dot = []byte(".") + slash = []byte("/") +) + +// stack returns a nicely formated stack frame, skipping skip frames +func stack(skip int) []byte { + buf := new(bytes.Buffer) // the returned data + // As we loop, we open files and read them. These variables record the currently + // loaded file. + var lines [][]byte + var lastFile string + for i := skip; ; i++ { // Skip the expected number of frames + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + // Print this much at least. If we can't find the source, it won't show. + fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) + if file != lastFile { + data, err := ioutil.ReadFile(file) + if err != nil { + continue + } + lines = bytes.Split(data, []byte{'\n'}) + lastFile = file + } + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) + } + return buf.Bytes() +} + +// source returns a space-trimmed slice of the n'th line. +func source(lines [][]byte, n int) []byte { + n-- // in stack trace, lines are 1-indexed but our array is 0-indexed + if n < 0 || n >= len(lines) { + return dunno + } + return bytes.TrimSpace(lines[n]) +} + +// function returns, if possible, the name of the function containing the PC. +func function(pc uintptr) []byte { + fn := runtime.FuncForPC(pc) + if fn == nil { + return dunno + } + name := []byte(fn.Name()) + // The name includes the path name to the package, which is unnecessary + // since the file name is already included. Plus, it has center dots. + // That is, we see + // runtime/debug.*T·ptrmethod + // and want + // *T.ptrmethod + // Also the package path might contains dot (e.g. code.google.com/...), + // so first eliminate the path prefix + if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { + name = name[lastslash+1:] + } + if period := bytes.Index(name, dot); period >= 0 { + name = name[period+1:] + } + name = bytes.Replace(name, centerDot, dot, -1) + return name +} + +// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. +// While Martini is in development mode, Recovery will also output the panic as HTML. +func Recovery() HandlerFunc { + return func(c *Context) { + defer func() { + if err := recover(); err != nil { + stack := stack(3) + log.Printf("PANIC: %s\n%s", err, stack) + c.Writer.WriteHeader(http.StatusInternalServerError) + } + }() + + c.Next() + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery_test.go new file mode 100644 index 00000000..f9047e24 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/recovery_test.go @@ -0,0 +1,56 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "log" + "os" + "testing" +) + +// TestPanicInHandler assert that panic has been recovered. +func TestPanicInHandler(t *testing.T) { + // SETUP + log.SetOutput(bytes.NewBuffer(nil)) // Disable panic logs for testing + r := New() + r.Use(Recovery()) + r.GET("/recovery", func(_ *Context) { + panic("Oupps, Houston, we have a problem") + }) + + // RUN + w := PerformRequest(r, "GET", "/recovery") + + // restore logging + log.SetOutput(os.Stderr) + + if w.Code != 500 { + t.Errorf("Response code should be Internal Server Error, was: %s", w.Code) + } +} + +// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. +func TestPanicWithAbort(t *testing.T) { + // SETUP + log.SetOutput(bytes.NewBuffer(nil)) + r := New() + r.Use(Recovery()) + r.GET("/recovery", func(c *Context) { + c.AbortWithStatus(400) + panic("Oupps, Houston, we have a problem") + }) + + // RUN + w := PerformRequest(r, "GET", "/recovery") + + // restore logging + log.SetOutput(os.Stderr) + + // TEST + if w.Code != 500 { + t.Errorf("Response code should be Bad request, was: %s", w.Code) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/render/render.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/render/render.go new file mode 100644 index 00000000..467a3299 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/render/render.go @@ -0,0 +1,123 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "html/template" + "net/http" +) + +type ( + Render interface { + Render(http.ResponseWriter, int, ...interface{}) error + } + + // JSON binding + jsonRender struct{} + + // XML binding + xmlRender struct{} + + // Plain text + plainRender struct{} + + // Redirects + redirectRender struct{} + + // Redirects + htmlDebugRender struct { + files []string + globs []string + } + + // form binding + HTMLRender struct { + Template *template.Template + } +) + +var ( + JSON = jsonRender{} + XML = xmlRender{} + Plain = plainRender{} + Redirect = redirectRender{} + HTMLDebug = &htmlDebugRender{} +) + +func writeHeader(w http.ResponseWriter, code int, contentType string) { + w.Header().Set("Content-Type", contentType+"; charset=utf-8") + w.WriteHeader(code) +} + +func (_ jsonRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "application/json") + encoder := json.NewEncoder(w) + return encoder.Encode(data[0]) +} + +func (_ redirectRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + w.Header().Set("Location", data[0].(string)) + w.WriteHeader(code) + return nil +} + +func (_ xmlRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "application/xml") + encoder := xml.NewEncoder(w) + return encoder.Encode(data[0]) +} + +func (_ plainRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "text/plain") + format := data[0].(string) + args := data[1].([]interface{}) + var err error + if len(args) > 0 { + _, err = w.Write([]byte(fmt.Sprintf(format, args...))) + } else { + _, err = w.Write([]byte(format)) + } + return err +} + +func (r *htmlDebugRender) AddGlob(pattern string) { + r.globs = append(r.globs, pattern) +} + +func (r *htmlDebugRender) AddFiles(files ...string) { + r.files = append(r.files, files...) +} + +func (r *htmlDebugRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "text/html") + file := data[0].(string) + obj := data[1] + + t := template.New("") + + if len(r.files) > 0 { + if _, err := t.ParseFiles(r.files...); err != nil { + return err + } + } + + for _, glob := range r.globs { + if _, err := t.ParseGlob(glob); err != nil { + return err + } + } + + return t.ExecuteTemplate(w, file, obj) +} + +func (html HTMLRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "text/html") + file := data[0].(string) + obj := data[1] + return html.Template.ExecuteTemplate(w, file, obj) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/response_writer.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/response_writer.go new file mode 100644 index 00000000..98993958 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/response_writer.go @@ -0,0 +1,100 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bufio" + "errors" + "log" + "net" + "net/http" +) + +const ( + NoWritten = -1 +) + +type ( + ResponseWriter interface { + http.ResponseWriter + http.Hijacker + http.Flusher + http.CloseNotifier + + Status() int + Size() int + Written() bool + WriteHeaderNow() + } + + responseWriter struct { + http.ResponseWriter + status int + size int + } +) + +func (w *responseWriter) reset(writer http.ResponseWriter) { + w.ResponseWriter = writer + w.status = 200 + w.size = NoWritten +} + +func (w *responseWriter) WriteHeader(code int) { + if code > 0 { + w.status = code + if w.Written() { + log.Println("[GIN] WARNING. Headers were already written!") + } + } +} + +func (w *responseWriter) WriteHeaderNow() { + if !w.Written() { + w.size = 0 + w.ResponseWriter.WriteHeader(w.status) + } +} + +func (w *responseWriter) Write(data []byte) (n int, err error) { + w.WriteHeaderNow() + n, err = w.ResponseWriter.Write(data) + w.size += n + return +} + +func (w *responseWriter) Status() int { + return w.status +} + +func (w *responseWriter) Size() int { + return w.size +} + +func (w *responseWriter) Written() bool { + return w.size != NoWritten +} + +// Implements the http.Hijacker interface +func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hijacker, ok := w.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface") + } + return hijacker.Hijack() +} + +// Implements the http.CloseNotify interface +func (w *responseWriter) CloseNotify() <-chan bool { + return w.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + +// Implements the http.Flush interface +func (w *responseWriter) Flush() { + flusher, ok := w.ResponseWriter.(http.Flusher) + if ok { + flusher.Flush() + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/routergroup.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/routergroup.go new file mode 100644 index 00000000..8e02a402 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/routergroup.go @@ -0,0 +1,148 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "github.com/julienschmidt/httprouter" + "net/http" + "path" +) + +// Used internally to configure router, a RouterGroup is associated with a prefix +// and an array of handlers (middlewares) +type RouterGroup struct { + Handlers []HandlerFunc + absolutePath string + engine *Engine +} + +// Adds middlewares to the group, see example code in github. +func (group *RouterGroup) Use(middlewares ...HandlerFunc) { + group.Handlers = append(group.Handlers, middlewares...) +} + +// Creates a new router group. You should add all the routes that have common middlwares or the same path prefix. +// For example, all the routes that use a common middlware for authorization could be grouped. +func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { + return &RouterGroup{ + Handlers: group.combineHandlers(handlers), + absolutePath: group.calculateAbsolutePath(relativePath), + engine: group.engine, + } +} + +// Handle registers a new request handle and middlewares with the given path and method. +// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes. +// See the example code in github. +// +// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut +// functions can be used. +// +// This function is intended for bulk loading and to allow the usage of less +// frequently used, non-standardized or custom methods (e.g. for internal +// communication with a proxy). +func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers []HandlerFunc) { + absolutePath := group.calculateAbsolutePath(relativePath) + handlers = group.combineHandlers(handlers) + if IsDebugging() { + nuHandlers := len(handlers) + handlerName := nameOfFunction(handlers[nuHandlers-1]) + debugPrint("%-5s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + group.engine.router.Handle(httpMethod, absolutePath, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { + context := group.engine.createContext(w, req, params, handlers) + context.Next() + context.Writer.WriteHeaderNow() + group.engine.reuseContext(context) + }) +} + +// POST is a shortcut for router.Handle("POST", path, handle) +func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) { + group.Handle("POST", relativePath, handlers) +} + +// GET is a shortcut for router.Handle("GET", path, handle) +func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) { + group.Handle("GET", relativePath, handlers) +} + +// DELETE is a shortcut for router.Handle("DELETE", path, handle) +func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) { + group.Handle("DELETE", relativePath, handlers) +} + +// PATCH is a shortcut for router.Handle("PATCH", path, handle) +func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) { + group.Handle("PATCH", relativePath, handlers) +} + +// PUT is a shortcut for router.Handle("PUT", path, handle) +func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) { + group.Handle("PUT", relativePath, handlers) +} + +// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) +func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) { + group.Handle("OPTIONS", relativePath, handlers) +} + +// HEAD is a shortcut for router.Handle("HEAD", path, handle) +func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) { + group.Handle("HEAD", relativePath, handlers) +} + +// LINK is a shortcut for router.Handle("LINK", path, handle) +func (group *RouterGroup) LINK(relativePath string, handlers ...HandlerFunc) { + group.Handle("LINK", relativePath, handlers) +} + +// UNLINK is a shortcut for router.Handle("UNLINK", path, handle) +func (group *RouterGroup) UNLINK(relativePath string, handlers ...HandlerFunc) { + group.Handle("UNLINK", relativePath, handlers) +} + +// Static serves files from the given file system root. +// Internally a http.FileServer is used, therefore http.NotFound is used instead +// of the Router's NotFound handler. +// To use the operating system's file system implementation, +// use : +// router.Static("/static", "/var/www") +func (group *RouterGroup) Static(relativePath, root string) { + absolutePath := group.calculateAbsolutePath(relativePath) + handler := group.createStaticHandler(absolutePath, root) + absolutePath = path.Join(absolutePath, "/*filepath") + + // Register GET and HEAD handlers + group.GET(absolutePath, handler) + group.HEAD(absolutePath, handler) +} + +func (group *RouterGroup) createStaticHandler(absolutePath, root string) func(*Context) { + fileServer := http.StripPrefix(absolutePath, http.FileServer(http.Dir(root))) + return func(c *Context) { + fileServer.ServeHTTP(c.Writer, c.Request) + } +} + +func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc { + finalSize := len(group.Handlers) + len(handlers) + mergedHandlers := make([]HandlerFunc, 0, finalSize) + mergedHandlers = append(mergedHandlers, group.Handlers...) + return append(mergedHandlers, handlers...) +} + +func (group *RouterGroup) calculateAbsolutePath(relativePath string) string { + if len(relativePath) == 0 { + return group.absolutePath + } + absolutePath := path.Join(group.absolutePath, relativePath) + appendSlash := lastChar(relativePath) == '/' && lastChar(absolutePath) != '/' + if appendSlash { + return absolutePath + "/" + } + return absolutePath +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/utils.go b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/utils.go new file mode 100644 index 00000000..43ddaecd --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/gin-gonic/gin/utils.go @@ -0,0 +1,82 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "encoding/xml" + "reflect" + "runtime" + "strings" +) + +type H map[string]interface{} + +// Allows type H to be used with xml.Marshal +func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name = xml.Name{ + Space: "", + Local: "map", + } + if err := e.EncodeToken(start); err != nil { + return err + } + for key, value := range h { + elem := xml.StartElement{ + Name: xml.Name{Space: "", Local: key}, + Attr: []xml.Attr{}, + } + if err := e.EncodeElement(value, elem); err != nil { + return err + } + } + if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { + return err + } + return nil +} + +func filterFlags(content string) string { + for i, char := range content { + if char == ' ' || char == ';' { + return content[:i] + } + } + return content +} + +func chooseData(custom, wildcard interface{}) interface{} { + if custom == nil { + if wildcard == nil { + panic("negotiation config is invalid") + } + return wildcard + } + return custom +} + +func parseAccept(accept string) []string { + parts := strings.Split(accept, ",") + for i, part := range parts { + index := strings.IndexByte(part, ';') + if index >= 0 { + part = part[0:index] + } + part = strings.TrimSpace(part) + parts[i] = part + } + return parts +} + +func lastChar(str string) uint8 { + size := len(str) + if size == 0 { + panic("The length of the string can't be 0") + } + return str[size-1] +} + +func nameOfFunction(f interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/.travis.yml b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/.travis.yml new file mode 100644 index 00000000..b0cf7823 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/.travis.yml @@ -0,0 +1,6 @@ +language: go +go: + - 1.1 + - 1.2 + - 1.3 + - tip diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/LICENSE new file mode 100644 index 00000000..b829abc8 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013 Julien Schmidt. All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of the contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/README.md new file mode 100644 index 00000000..082e4cf2 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/README.md @@ -0,0 +1,234 @@ +# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter) + +HttpRouter is a lightweight high performance HTTP request router +(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/). + +In contrast to the default mux of Go's net/http package, this router supports +variables in the routing pattern and matches against the request method. +It also scales better. + +The router is optimized for best performance and a small memory footprint. +It scales well even with very long paths and a large number of routes. +A compressing dynamic trie (radix tree) structure is used for efficient matching. + +## Features +**Zero Garbage:** The matching and dispatching process generates zero bytes of +garbage. In fact, the only heap allocations that are made, is by building the +slice of the key-value pairs for path parameters. If the request path contains +no parameters, not a single heap allocation is necessary. + +**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). +See below for technical details of the implementation. + +**Parameters in your routing pattern:** Stop parsing the requested URL path, +just give the path segment a name and the router delivers the dynamic value to +you. Because of the design of the router, path parameters are very cheap. + +**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux), +a requested URL path could match multiple patterns. Therefore they have some +awkward pattern priority rules, like *longest match* or *first registered, +first matched*. By design of this router, a request can only match exactly one +or no route. As a result, there are also no unintended matches, which makes it +great for SEO and improves the user experience. + +**Stop caring about trailing slashes:** Choose the URL style you like, the +router automatically redirects the client if a trailing slash is missing or if +there is one extra. Of course it only does so, if the new path has a handler. +If you don't like it, you can turn off this behavior. + +**Path auto-correction:** Besides detecting the missing or additional trailing +slash at no extra cost, the router can also fix wrong cases and remove +superfluous path elements (like `../` or `//`). +Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? +HttpRouter can help him by making a case-insensitive look-up and redirecting him +to the correct URL. + +**No more server crashes:** You can set a PanicHandler to deal with panics +occurring during handling a HTTP request. The router then recovers and lets the +PanicHandler log what happened and deliver a nice error page. + +Of course you can also set a **custom NotFound handler** and **serve static files**. + +## Usage +This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details. + +Let's start with a trivial example: +```go +package main + +import ( + "fmt" + "github.com/julienschmidt/httprouter" + "net/http" + "log" +) + +func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Welcome!\n") +} + +func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) +} + +func main() { + router := httprouter.New() + router.GET("/", Index) + router.GET("/hello/:name", Hello) + + log.Fatal(http.ListenAndServe(":8080", router)) +} +``` + +### Named parameters +As you can see, `:name` is a *named parameter*. +The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. +You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: +`:name` can be retrived by `ByName("name")`. + +Named parameters only match a single path segment: +``` +Pattern: /user/:user + + /user/gordon match + /user/you match + /user/gordon/profile no match + /user/ no match +``` + +**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other. + +### Catch-All parameters +The second type are *catch-all* parameters and have the form `*name`. +Like the name suggests, they match everything. +Therefore they must always be at the **end** of the pattern: +``` +Pattern: /src/*filepath + + /src/ match + /src/somefile.go match + /src/subdir/somefile.go match +``` + +## How does it work? +The router relies on a tree structure which makes heavy use of *common prefixes*, +it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie) +(or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)). +Nodes with a common prefix also share a common parent. Here is a short example +what the routing tree for the `GET` request method could look like: + +``` +Priority Path Handle +9 \ *<1> +3 ├s nil +2 |├earch\ *<2> +1 |└upport\ *<3> +2 ├blog\ *<4> +1 | └:post nil +1 | └\ *<5> +2 ├about-us\ *<6> +1 | └team\ *<7> +1 └contact\ *<8> +``` +Every `*` represents the memory address of a handler function (a pointer). +If you follow a path trough the tree from the root to the leaf, you get the +complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder +([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a +tree structure also allows us to use dynamic parts like the `:post` parameter, +since we actually match against the routing patterns instead of just comparing +hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), +this works very well and efficient. + +Since URL paths have a hierarchical structure and make use only of a limited set +of characters (byte values), it is very likely that there are a lot of common +prefixes. This allows us to easily reduce the routing into ever smaller problems. +Moreover the router manages a separate tree for every request method. +For one thing it is more space efficient than holding a method->handle map in +every single node, for another thing is also allows us to greatly reduce the +routing problem before even starting the look-up in the prefix-tree. + +For even better scalability, the child nodes on each tree level are ordered by +priority, where the priority is just the number of handles registered in sub +nodes (children, grandchildren, and so on..). +This helps in two ways: + +1. Nodes which are part of the most routing paths are evaluated first. This +helps to make as much routes as possible to be reachable as fast as possible. +2. It is some sort of cost compensation. The longest reachable path (highest +cost) can always be evaluated first. The following scheme visualizes the tree +structure. Nodes are evaluated from top to bottom and from left to right. + +``` +├------------ +├--------- +├----- +├---- +├-- +├-- +└- +``` + + +## Why doesn't this work with http.Handler? +**It does!** The router itself implements the http.Handler interface. +Moreover the router provides convenient [adapters for http.Handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [http.HandlerFunc](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s +which allows them to be used as a [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route. +The only disadvantage is, that no parameter values can be retrieved when a +http.Handler or http.HandlerFunc is used, since there is no efficient way to +pass the values with the existing function parameters. +Therefore [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) has a third function parameter. + +Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up. + + +## Where can I find Middleware *X*? +This package just provides a very efficient request router with a few extra +features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler), +you can chain any http.Handler compatible middleware before the router, +for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). +Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), +it's very easy! + +Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks-building-upon-httprouter). + +Here is a quick example: Does your server serve multiple domains / hosts? +You want to use sub-domains? +Define a router per host! +```go +// We need an object that implements the http.Handler interface. +// Therefore we need a type for which we implement the ServeHTTP method. +// We just use a map here, in which we map host names (with port) to http.Handlers +type HostSwitch map[string]http.Handler + +// Implement the ServerHTTP method on our new type +func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Check if a http.Handler is registered for the given host. + // If yes, use it to handle the request. + if handler := hs[r.Host]; handler != nil { + handler.ServeHTTP(w, r) + } else { + // Handle host names for wich no handler is registered + http.Error(w, "Forbidden", 403) // Or Redirect? + } +} + +func main() { + // Initialize a router as usual + router := httprouter.New() + router.GET("/", Index) + router.GET("/hello/:name", Hello) + + // Make a new HostSwitch and insert the router (our http handler) + // for example.com and port 12345 + hs := make(HostSwitch) + hs["example.com:12345"] = router + + // Use the HostSwitch to listen and serve on port 12345 + log.Fatal(http.ListenAndServe(":12345", hs)) +} +``` + +## Web Frameworks building upon HttpRouter +If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package: +* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance +* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path.go new file mode 100644 index 00000000..486134db --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path.go @@ -0,0 +1,123 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Based on the path package, Copyright 2009 The Go Authors. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package httprouter + +// CleanPath is the URL version of path.Clean, it returns a canonical URL path +// for p, eliminating . and .. elements. +// +// The following rules are applied iteratively until no further processing can +// be done: +// 1. Replace multiple slashes with a single slash. +// 2. Eliminate each . path name element (the current directory). +// 3. Eliminate each inner .. path name element (the parent directory) +// along with the non-.. element that precedes it. +// 4. Eliminate .. elements that begin a rooted path: +// that is, replace "/.." by "/" at the beginning of a path. +// +// If the result of this process is an empty string, "/" is returned +func CleanPath(p string) string { + // Turn empty string into "/" + if p == "" { + return "/" + } + + n := len(p) + var buf []byte + + // Invariants: + // reading from path; r is index of next byte to process. + // writing to buf; w is index of next byte to write. + + // path must start with '/' + r := 1 + w := 1 + + if p[0] != '/' { + r = 0 + buf = make([]byte, n+1) + buf[0] = '/' + } + + trailing := n > 2 && p[n-1] == '/' + + // A bit more clunky without a 'lazybuf' like the path package, but the loop + // gets completely inlined (bufApp). So in contrast to the path package this + // loop has no expensive function calls (except 1x make) + + for r < n { + switch { + case p[r] == '/': + // empty path element, trailing slash is added after the end + r++ + + case p[r] == '.' && r+1 == n: + trailing = true + r++ + + case p[r] == '.' && p[r+1] == '/': + // . element + r++ + + case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): + // .. element: remove to last / + r += 2 + + if w > 1 { + // can backtrack + w-- + + if buf == nil { + for w > 1 && p[w] != '/' { + w-- + } + } else { + for w > 1 && buf[w] != '/' { + w-- + } + } + } + + default: + // real path element. + // add slash if needed + if w > 1 { + bufApp(&buf, p, w, '/') + w++ + } + + // copy element + for r < n && p[r] != '/' { + bufApp(&buf, p, w, p[r]) + w++ + r++ + } + } + } + + // re-append trailing slash + if trailing && w > 1 { + bufApp(&buf, p, w, '/') + w++ + } + + if buf == nil { + return p[:w] + } + return string(buf[:w]) +} + +// internal helper to lazily create a buffer if necessary +func bufApp(buf *[]byte, s string, w int, c byte) { + if *buf == nil { + if s[w] == c { + return + } + + *buf = make([]byte, len(s)) + copy(*buf, s[:w]) + } + (*buf)[w] = c +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path_test.go new file mode 100644 index 00000000..c4ceda5d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/path_test.go @@ -0,0 +1,92 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Based on the path package, Copyright 2009 The Go Authors. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package httprouter + +import ( + "runtime" + "testing" +) + +var cleanTests = []struct { + path, result string +}{ + // Already clean + {"/", "/"}, + {"/abc", "/abc"}, + {"/a/b/c", "/a/b/c"}, + {"/abc/", "/abc/"}, + {"/a/b/c/", "/a/b/c/"}, + + // missing root + {"", "/"}, + {"abc", "/abc"}, + {"abc/def", "/abc/def"}, + {"a/b/c", "/a/b/c"}, + + // Remove doubled slash + {"//", "/"}, + {"/abc//", "/abc/"}, + {"/abc/def//", "/abc/def/"}, + {"/a/b/c//", "/a/b/c/"}, + {"/abc//def//ghi", "/abc/def/ghi"}, + {"//abc", "/abc"}, + {"///abc", "/abc"}, + {"//abc//", "/abc/"}, + + // Remove . elements + {".", "/"}, + {"./", "/"}, + {"/abc/./def", "/abc/def"}, + {"/./abc/def", "/abc/def"}, + {"/abc/.", "/abc/"}, + + // Remove .. elements + {"..", "/"}, + {"../", "/"}, + {"../../", "/"}, + {"../..", "/"}, + {"../../abc", "/abc"}, + {"/abc/def/ghi/../jkl", "/abc/def/jkl"}, + {"/abc/def/../ghi/../jkl", "/abc/jkl"}, + {"/abc/def/..", "/abc"}, + {"/abc/def/../..", "/"}, + {"/abc/def/../../..", "/"}, + {"/abc/def/../../..", "/"}, + {"/abc/def/../../../ghi/jkl/../../../mno", "/mno"}, + + // Combinations + {"abc/./../def", "/def"}, + {"abc//./../def", "/def"}, + {"abc/../../././../def", "/def"}, +} + +func TestPathClean(t *testing.T) { + for _, test := range cleanTests { + if s := CleanPath(test.path); s != test.result { + t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result) + } + if s := CleanPath(test.result); s != test.result { + t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result) + } + } +} + +func TestPathCleanMallocs(t *testing.T) { + if testing.Short() { + t.Skip("skipping malloc count in short mode") + } + if runtime.GOMAXPROCS(0) > 1 { + t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") + return + } + + for _, test := range cleanTests { + allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) }) + if allocs > 0 { + t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router.go new file mode 100644 index 00000000..3f3d1639 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router.go @@ -0,0 +1,317 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +// Package httprouter is a trie based high performance HTTP request router. +// +// A trivial example is: +// +// package main +// +// import ( +// "fmt" +// "github.com/julienschmidt/httprouter" +// "net/http" +// "log" +// ) +// +// func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +// fmt.Fprint(w, "Welcome!\n") +// } +// +// func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +// fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) +// } +// +// func main() { +// router := httprouter.New() +// router.GET("/", Index) +// router.GET("/hello/:name", Hello) +// +// log.Fatal(http.ListenAndServe(":8080", router)) +// } +// +// The router matches incoming requests by the request method and the path. +// If a handle is registered for this path and method, the router delegates the +// request to that function. +// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to +// register handles, for all other methods router.Handle can be used. +// +// The registered path, against which the router matches incoming requests, can +// contain two types of parameters: +// Syntax Type +// :name named parameter +// *name catch-all parameter +// +// Named parameters are dynamic path segments. They match anything until the +// next '/' or the path end: +// Path: /blog/:category/:post +// +// Requests: +// /blog/go/request-routers match: category="go", post="request-routers" +// /blog/go/request-routers/ no match, but the router would redirect +// /blog/go/ no match +// /blog/go/request-routers/comments no match +// +// Catch-all parameters match anything until the path end, including the +// directory index (the '/' before the catch-all). Since they match anything +// until the end, catch-all paramerters must always be the final path element. +// Path: /files/*filepath +// +// Requests: +// /files/ match: filepath="/" +// /files/LICENSE match: filepath="/LICENSE" +// /files/templates/article.html match: filepath="/templates/article.html" +// /files no match, but the router would redirect +// +// The value of parameters is saved as a slice of the Param struct, consisting +// each of a key and a value. The slice is passed to the Handle func as a third +// parameter. +// There are two ways to retrieve the value of a parameter: +// // by the name of the parameter +// user := ps.ByName("user") // defined by :user or *user +// +// // by the index of the parameter. This way you can also get the name (key) +// thirdKey := ps[2].Key // the name of the 3rd parameter +// thirdValue := ps[2].Value // the value of the 3rd parameter +package httprouter + +import ( + "net/http" +) + +// Handle is a function that can be registered to a route to handle HTTP +// requests. Like http.HandlerFunc, but has a third parameter for the values of +// wildcards (variables). +type Handle func(http.ResponseWriter, *http.Request, Params) + +// Param is a single URL parameter, consisting of a key and a value. +type Param struct { + Key string + Value string +} + +// Params is a Param-slice, as returned by the router. +// The slice is ordered, the first URL parameter is also the first slice value. +// It is therefore safe to read values by the index. +type Params []Param + +// ByName returns the value of the first Param which key matches the given name. +// If no matching Param is found, an empty string is returned. +func (ps Params) ByName(name string) string { + for i := range ps { + if ps[i].Key == name { + return ps[i].Value + } + } + return "" +} + +// Router is a http.Handler which can be used to dispatch requests to different +// handler functions via configurable routes +type Router struct { + trees map[string]*node + + // Enables automatic redirection if the current route can't be matched but a + // handler for the path with (without) the trailing slash exists. + // For example if /foo/ is requested but a route only exists for /foo, the + // client is redirected to /foo with http status code 301 for GET requests + // and 307 for all other request methods. + RedirectTrailingSlash bool + + // If enabled, the router tries to fix the current request path, if no + // handle is registered for it. + // First superfluous path elements like ../ or // are removed. + // Afterwards the router does a case-insensitive lookup of the cleaned path. + // If a handle can be found for this route, the router makes a redirection + // to the corrected path with status code 301 for GET requests and 307 for + // all other request methods. + // For example /FOO and /..//Foo could be redirected to /foo. + // RedirectTrailingSlash is independent of this option. + RedirectFixedPath bool + + // Configurable http.HandlerFunc which is called when no matching route is + // found. If it is not set, http.NotFound is used. + NotFound http.HandlerFunc + + // Function to handle panics recovered from http handlers. + // It should be used to generate a error page and return the http error code + // 500 (Internal Server Error). + // The handler can be used to keep your server from crashing because of + // unrecovered panics. + PanicHandler func(http.ResponseWriter, *http.Request, interface{}) +} + +// Make sure the Router conforms with the http.Handler interface +var _ http.Handler = New() + +// New returns a new initialized Router. +// Path auto-correction, including trailing slashes, is enabled by default. +func New() *Router { + return &Router{ + RedirectTrailingSlash: true, + RedirectFixedPath: true, + } +} + +// GET is a shortcut for router.Handle("GET", path, handle) +func (r *Router) GET(path string, handle Handle) { + r.Handle("GET", path, handle) +} + +// POST is a shortcut for router.Handle("POST", path, handle) +func (r *Router) POST(path string, handle Handle) { + r.Handle("POST", path, handle) +} + +// PUT is a shortcut for router.Handle("PUT", path, handle) +func (r *Router) PUT(path string, handle Handle) { + r.Handle("PUT", path, handle) +} + +// PATCH is a shortcut for router.Handle("PATCH", path, handle) +func (r *Router) PATCH(path string, handle Handle) { + r.Handle("PATCH", path, handle) +} + +// DELETE is a shortcut for router.Handle("DELETE", path, handle) +func (r *Router) DELETE(path string, handle Handle) { + r.Handle("DELETE", path, handle) +} + +// Handle registers a new request handle with the given path and method. +// +// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut +// functions can be used. +// +// This function is intended for bulk loading and to allow the usage of less +// frequently used, non-standardized or custom methods (e.g. for internal +// communication with a proxy). +func (r *Router) Handle(method, path string, handle Handle) { + if path[0] != '/' { + panic("path must begin with '/'") + } + + if r.trees == nil { + r.trees = make(map[string]*node) + } + + root := r.trees[method] + if root == nil { + root = new(node) + r.trees[method] = root + } + + root.addRoute(path, handle) +} + +// Handler is an adapter which allows the usage of an http.Handler as a +// request handle. +func (r *Router) Handler(method, path string, handler http.Handler) { + r.Handle(method, path, + func(w http.ResponseWriter, req *http.Request, _ Params) { + handler.ServeHTTP(w, req) + }, + ) +} + +// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a +// request handle. +func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { + r.Handle(method, path, + func(w http.ResponseWriter, req *http.Request, _ Params) { + handler(w, req) + }, + ) +} + +// ServeFiles serves files from the given file system root. +// The path must end with "/*filepath", files are then served from the local +// path /defined/root/dir/*filepath. +// For example if root is "/etc" and *filepath is "passwd", the local file +// "/etc/passwd" would be served. +// Internally a http.FileServer is used, therefore http.NotFound is used instead +// of the Router's NotFound handler. +// To use the operating system's file system implementation, +// use http.Dir: +// router.ServeFiles("/src/*filepath", http.Dir("/var/www")) +func (r *Router) ServeFiles(path string, root http.FileSystem) { + if len(path) < 10 || path[len(path)-10:] != "/*filepath" { + panic("path must end with /*filepath") + } + + fileServer := http.FileServer(root) + + r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) { + req.URL.Path = ps.ByName("filepath") + fileServer.ServeHTTP(w, req) + }) +} + +func (r *Router) recv(w http.ResponseWriter, req *http.Request) { + if rcv := recover(); rcv != nil { + r.PanicHandler(w, req, rcv) + } +} + +// Lookup allows the manual lookup of a method + path combo. +// This is e.g. useful to build a framework around this router. +func (r *Router) Lookup(method, path string) (Handle, Params, bool) { + if root := r.trees[method]; root != nil { + return root.getValue(path) + } + return nil, nil, false +} + +// ServeHTTP makes the router implement the http.Handler interface. +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if r.PanicHandler != nil { + defer r.recv(w, req) + } + + if root := r.trees[req.Method]; root != nil { + path := req.URL.Path + + if handle, ps, tsr := root.getValue(path); handle != nil { + handle(w, req, ps) + return + } else if req.Method != "CONNECT" && path != "/" { + code := 301 // Permanent redirect, request with GET method + if req.Method != "GET" { + // Temporary redirect, request with same method + // As of Go 1.3, Go does not support status code 308. + code = 307 + } + + if tsr && r.RedirectTrailingSlash { + if path[len(path)-1] == '/' { + req.URL.Path = path[:len(path)-1] + } else { + req.URL.Path = path + "/" + } + http.Redirect(w, req, req.URL.String(), code) + return + } + + // Try to fix the request path + if r.RedirectFixedPath { + fixedPath, found := root.findCaseInsensitivePath( + CleanPath(path), + r.RedirectTrailingSlash, + ) + if found { + req.URL.Path = string(fixedPath) + http.Redirect(w, req, req.URL.String(), code) + return + } + } + } + } + + // Handle 404 + if r.NotFound != nil { + r.NotFound(w, req) + } else { + http.NotFound(w, req) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router_test.go new file mode 100644 index 00000000..ca59066f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/router_test.go @@ -0,0 +1,329 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package httprouter + +import ( + "errors" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +type mockResponseWriter struct{} + +func (m *mockResponseWriter) Header() (h http.Header) { + return http.Header{} +} + +func (m *mockResponseWriter) Write(p []byte) (n int, err error) { + return len(p), nil +} + +func (m *mockResponseWriter) WriteString(s string) (n int, err error) { + return len(s), nil +} + +func (m *mockResponseWriter) WriteHeader(int) {} + +func TestParams(t *testing.T) { + ps := Params{ + Param{"param1", "value1"}, + Param{"param2", "value2"}, + Param{"param3", "value3"}, + } + for i := range ps { + if val := ps.ByName(ps[i].Key); val != ps[i].Value { + t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value) + } + } + if val := ps.ByName("noKey"); val != "" { + t.Errorf("Expected empty string for not found key; got: %s", val) + } +} + +func TestRouter(t *testing.T) { + router := New() + + routed := false + router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) { + routed = true + want := Params{Param{"name", "gopher"}} + if !reflect.DeepEqual(ps, want) { + t.Fatalf("wrong wildcard values: want %v, got %v", want, ps) + } + }) + + w := new(mockResponseWriter) + + req, _ := http.NewRequest("GET", "/user/gopher", nil) + router.ServeHTTP(w, req) + + if !routed { + t.Fatal("routing failed") + } +} + +type handlerStruct struct { + handeled *bool +} + +func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { + *h.handeled = true +} + +func TestRouterAPI(t *testing.T) { + var get, post, put, patch, delete, handler, handlerFunc bool + + httpHandler := handlerStruct{&handler} + + router := New() + router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { + get = true + }) + router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { + post = true + }) + router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) { + put = true + }) + router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) { + patch = true + }) + router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) { + delete = true + }) + router.Handler("GET", "/Handler", httpHandler) + router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) { + handlerFunc = true + }) + + w := new(mockResponseWriter) + + r, _ := http.NewRequest("GET", "/GET", nil) + router.ServeHTTP(w, r) + if !get { + t.Error("routing GET failed") + } + + r, _ = http.NewRequest("POST", "/POST", nil) + router.ServeHTTP(w, r) + if !post { + t.Error("routing POST failed") + } + + r, _ = http.NewRequest("PUT", "/PUT", nil) + router.ServeHTTP(w, r) + if !put { + t.Error("routing PUT failed") + } + + r, _ = http.NewRequest("PATCH", "/PATCH", nil) + router.ServeHTTP(w, r) + if !patch { + t.Error("routing PATCH failed") + } + + r, _ = http.NewRequest("DELETE", "/DELETE", nil) + router.ServeHTTP(w, r) + if !delete { + t.Error("routing DELETE failed") + } + + r, _ = http.NewRequest("GET", "/Handler", nil) + router.ServeHTTP(w, r) + if !handler { + t.Error("routing Handler failed") + } + + r, _ = http.NewRequest("GET", "/HandlerFunc", nil) + router.ServeHTTP(w, r) + if !handlerFunc { + t.Error("routing HandlerFunc failed") + } +} + +func TestRouterRoot(t *testing.T) { + router := New() + recv := catchPanic(func() { + router.GET("noSlashRoot", nil) + }) + if recv == nil { + t.Fatal("registering path not beginning with '/' did not panic") + } +} + +func TestRouterNotFound(t *testing.T) { + handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {} + + router := New() + router.GET("/path", handlerFunc) + router.GET("/dir/", handlerFunc) + + testRoutes := []struct { + route string + code int + header string + }{ + {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ + {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ + {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case + {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case + {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ + {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ + {"/../path", 301, "map[Location:[/path]]"}, // CleanPath + {"/nope", 404, ""}, // NotFound + } + for _, tr := range testRoutes { + r, _ := http.NewRequest("GET", tr.route, nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) { + t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header()) + } + } + + // Test custom not found handler + var notFound bool + router.NotFound = func(rw http.ResponseWriter, r *http.Request) { + rw.WriteHeader(404) + notFound = true + } + r, _ := http.NewRequest("GET", "/nope", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + if !(w.Code == 404 && notFound == true) { + t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) + } + + // Test other method than GET (want 307 instead of 301) + router.PATCH("/path", handlerFunc) + r, _ = http.NewRequest("PATCH", "/path/", nil) + w = httptest.NewRecorder() + router.ServeHTTP(w, r) + if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") { + t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header()) + } + + // Test special case where no node for the prefix "/" exists + router = New() + router.GET("/a", handlerFunc) + r, _ = http.NewRequest("GET", "/", nil) + w = httptest.NewRecorder() + router.ServeHTTP(w, r) + if !(w.Code == 404) { + t.Errorf("NotFound handling route / failed: Code=%d", w.Code) + } +} + +func TestRouterPanicHandler(t *testing.T) { + router := New() + panicHandled := false + + router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) { + panicHandled = true + } + + router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) { + panic("oops!") + }) + + w := new(mockResponseWriter) + req, _ := http.NewRequest("PUT", "/user/gopher", nil) + + defer func() { + if rcv := recover(); rcv != nil { + t.Fatal("handling panic failed") + } + }() + + router.ServeHTTP(w, req) + + if !panicHandled { + t.Fatal("simulating failed") + } +} + +func TestRouterLookup(t *testing.T) { + routed := false + wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) { + routed = true + } + wantParams := Params{Param{"name", "gopher"}} + + router := New() + + // try empty router first + handle, _, tsr := router.Lookup("GET", "/nope") + if handle != nil { + t.Fatalf("Got handle for unregistered pattern: %v", handle) + } + if tsr { + t.Error("Got wrong TSR recommendation!") + } + + // insert route and try again + router.GET("/user/:name", wantHandle) + + handle, params, tsr := router.Lookup("GET", "/user/gopher") + if handle == nil { + t.Fatal("Got no handle!") + } else { + handle(nil, nil, nil) + if !routed { + t.Fatal("Routing failed!") + } + } + + if !reflect.DeepEqual(params, wantParams) { + t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params) + } + + handle, _, tsr = router.Lookup("GET", "/user/gopher/") + if handle != nil { + t.Fatalf("Got handle for unregistered pattern: %v", handle) + } + if !tsr { + t.Error("Got no TSR recommendation!") + } + + handle, _, tsr = router.Lookup("GET", "/nope") + if handle != nil { + t.Fatalf("Got handle for unregistered pattern: %v", handle) + } + if tsr { + t.Error("Got wrong TSR recommendation!") + } +} + +type mockFileSystem struct { + opened bool +} + +func (mfs *mockFileSystem) Open(name string) (http.File, error) { + mfs.opened = true + return nil, errors.New("this is just a mock") +} + +func TestRouterServeFiles(t *testing.T) { + router := New() + mfs := &mockFileSystem{} + + recv := catchPanic(func() { + router.ServeFiles("/noFilepath", mfs) + }) + if recv == nil { + t.Fatal("registering path not ending with '*filepath' did not panic") + } + + router.ServeFiles("/*filepath", mfs) + w := new(mockResponseWriter) + r, _ := http.NewRequest("GET", "/favicon.ico", nil) + router.ServeHTTP(w, r) + if !mfs.opened { + t.Error("serving file failed") + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree.go new file mode 100644 index 00000000..933b5cbe --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree.go @@ -0,0 +1,534 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package httprouter + +import ( + "strings" + "unicode" +) + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +func countParams(path string) uint8 { + var n uint + for i := 0; i < len(path); i++ { + if path[i] != ':' && path[i] != '*' { + continue + } + n++ + } + if n >= 255 { + return 255 + } + return uint8(n) +} + +type nodeType uint8 + +const ( + static nodeType = 0 + param nodeType = 1 + catchAll nodeType = 2 +) + +type node struct { + path string + wildChild bool + nType nodeType + maxParams uint8 + indices []byte + children []*node + handle Handle + priority uint32 +} + +// increments priority of the given child and reorders if necessary +func (n *node) incrementChildPrio(i int) int { + n.children[i].priority++ + prio := n.children[i].priority + + // adjust position (move to front) + for j := i - 1; j >= 0 && n.children[j].priority < prio; j-- { + // swap node positions + tmpN := n.children[j] + n.children[j] = n.children[i] + n.children[i] = tmpN + tmpI := n.indices[j] + n.indices[j] = n.indices[i] + n.indices[i] = tmpI + + i-- + } + return i +} + +// addRoute adds a node with the given handle to the path. +// Not concurrency-safe! +func (n *node) addRoute(path string, handle Handle) { + n.priority++ + numParams := countParams(path) + + // non-empty tree + if len(n.path) > 0 || len(n.children) > 0 { + WALK: + for { + // Update maxParams of the current node + if numParams > n.maxParams { + n.maxParams = numParams + } + + // Find the longest common prefix. + // This also implies that the commom prefix contains no ':' or '*' + // since the existing key can't contain this chars. + i := 0 + for max := min(len(path), len(n.path)); i < max && path[i] == n.path[i]; i++ { + } + + // Split edge + if i < len(n.path) { + child := node{ + path: n.path[i:], + wildChild: n.wildChild, + indices: n.indices, + children: n.children, + handle: n.handle, + priority: n.priority - 1, + } + + // Update maxParams (max of all children) + for i := range child.children { + if child.children[i].maxParams > child.maxParams { + child.maxParams = child.children[i].maxParams + } + } + + n.children = []*node{&child} + n.indices = []byte{n.path[i]} + n.path = path[:i] + n.handle = nil + n.wildChild = false + } + + // Make new node a child of this node + if i < len(path) { + path = path[i:] + + if n.wildChild { + n = n.children[0] + n.priority++ + + // Update maxParams of the child node + if numParams > n.maxParams { + n.maxParams = numParams + } + numParams-- + + // Check if the wildcard matches + if len(path) >= len(n.path) && n.path == path[:len(n.path)] { + // check for longer wildcard, e.g. :name and :names + if len(n.path) >= len(path) || path[len(n.path)] == '/' { + continue WALK + } + } + + panic("conflict with wildcard route") + } + + c := path[0] + + // slash after param + if n.nType == param && c == '/' && len(n.children) == 1 { + n = n.children[0] + n.priority++ + continue WALK + } + + // Check if a child with the next path byte exists + for i, index := range n.indices { + if c == index { + i = n.incrementChildPrio(i) + n = n.children[i] + continue WALK + } + } + + // Otherwise insert it + if c != ':' && c != '*' { + n.indices = append(n.indices, c) + child := &node{ + maxParams: numParams, + } + n.children = append(n.children, child) + n.incrementChildPrio(len(n.indices) - 1) + n = child + } + n.insertChild(numParams, path, handle) + return + + } else if i == len(path) { // Make node a (in-path) leaf + if n.handle != nil { + panic("a Handle is already registered for this path") + } + n.handle = handle + } + return + } + } else { // Empty tree + n.insertChild(numParams, path, handle) + } +} + +func (n *node) insertChild(numParams uint8, path string, handle Handle) { + var offset int + + // find prefix until first wildcard (beginning with ':'' or '*'') + for i, max := 0, len(path); numParams > 0; i++ { + c := path[i] + if c != ':' && c != '*' { + continue + } + + // Check if this Node existing children which would be + // unreachable if we insert the wildcard here + if len(n.children) > 0 { + panic("wildcard route conflicts with existing children") + } + + // find wildcard end (either '/' or path end) + end := i + 1 + for end < max && path[end] != '/' { + end++ + } + + if end-i < 2 { + panic("wildcards must be named with a non-empty name") + } + + if c == ':' { // param + // split path at the beginning of the wildcard + if i > 0 { + n.path = path[offset:i] + offset = i + } + + child := &node{ + nType: param, + maxParams: numParams, + } + n.children = []*node{child} + n.wildChild = true + n = child + n.priority++ + numParams-- + + // if the path doesn't end with the wildcard, then there + // will be another non-wildcard subpath starting with '/' + if end < max { + n.path = path[offset:end] + offset = end + + child := &node{ + maxParams: numParams, + priority: 1, + } + n.children = []*node{child} + n = child + } + + } else { // catchAll + if end != max || numParams > 1 { + panic("catch-all routes are only allowed at the end of the path") + } + + if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { + panic("catch-all conflicts with existing handle for the path segment root") + } + + // currently fixed width 1 for '/' + i-- + if path[i] != '/' { + panic("no / before catch-all") + } + + n.path = path[offset:i] + + // first node: catchAll node with empty path + child := &node{ + wildChild: true, + nType: catchAll, + maxParams: 1, + } + n.children = []*node{child} + n.indices = []byte{path[i]} + n = child + n.priority++ + + // second node: node holding the variable + child = &node{ + path: path[i:], + nType: catchAll, + maxParams: 1, + handle: handle, + priority: 1, + } + n.children = []*node{child} + + return + } + } + + // insert remaining path part and handle to the leaf + n.path = path[offset:] + n.handle = handle +} + +// Returns the handle registered with the given path (key). The values of +// wildcards are saved to a map. +// If no handle can be found, a TSR (trailing slash redirect) recommendation is +// made if a handle exists with an extra (without the) trailing slash for the +// given path. +func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) { +walk: // Outer loop for walking the tree + for { + if len(path) > len(n.path) { + if path[:len(n.path)] == n.path { + path = path[len(n.path):] + // If this node does not have a wildcard (param or catchAll) + // child, we can just look up the next child node and continue + // to walk down the tree + if !n.wildChild { + c := path[0] + for i, index := range n.indices { + if c == index { + n = n.children[i] + continue walk + } + } + + // Nothing found. + // We can recommend to redirect to the same URL without a + // trailing slash if a leaf exists for that path. + tsr = (path == "/" && n.handle != nil) + return + + } + + // handle wildcard child + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ + } + + // save param value + if p == nil { + // lazy allocation + p = make(Params, 0, n.maxParams) + } + i := len(p) + p = p[:i+1] // expand slice within preallocated capacity + p[i].Key = n.path[1:] + p[i].Value = path[:end] + + // we need to go deeper! + if end < len(path) { + if len(n.children) > 0 { + path = path[end:] + n = n.children[0] + continue walk + } + + // ... but we can't + tsr = (len(path) == end+1) + return + } + + if handle = n.handle; handle != nil { + return + } else if len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists for TSR recommendation + n = n.children[0] + tsr = (n.path == "/" && n.handle != nil) + } + + return + + case catchAll: + // save param value + if p == nil { + // lazy allocation + p = make(Params, 0, n.maxParams) + } + i := len(p) + p = p[:i+1] // expand slice within preallocated capacity + p[i].Key = n.path[2:] + p[i].Value = path + + handle = n.handle + return + + default: + panic("Unknown node type") + } + } + } else if path == n.path { + // We should have reached the node containing the handle. + // Check if this node has a handle registered. + if handle = n.handle; handle != nil { + return + } + + // No handle found. Check if a handle for this path + a + // trailing slash exists for trailing slash recommendation + for i, index := range n.indices { + if index == '/' { + n = n.children[i] + tsr = (n.path == "/" && n.handle != nil) || + (n.nType == catchAll && n.children[0].handle != nil) + return + } + } + + return + } + + // Nothing found. We can recommend to redirect to the same URL with an + // extra trailing slash if a leaf exists for that path + tsr = (path == "/") || + (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && + path == n.path[:len(n.path)-1] && n.handle != nil) + return + } +} + +// Makes a case-insensitive lookup of the given path and tries to find a handler. +// It can optionally also fix trailing slashes. +// It returns the case-corrected path and a bool indicating wether the lookup +// was successful. +func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { + ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory + + // Outer loop for walking the tree + for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { + path = path[len(n.path):] + ciPath = append(ciPath, n.path...) + + if len(path) > 0 { + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree + if !n.wildChild { + r := unicode.ToLower(rune(path[0])) + for i, index := range n.indices { + // must use recursive approach since both index and + // ToLower(index) could exist. We must check both. + if r == unicode.ToLower(rune(index)) { + out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) + if found { + return append(ciPath, out...), true + } + } + } + + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + found = (fixTrailingSlash && path == "/" && n.handle != nil) + return + + } else { + n = n.children[0] + + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } + + // add param value to case insensitive path + ciPath = append(ciPath, path[:k]...) + + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + path = path[k:] + n = n.children[0] + continue + } else { // ... but we can't + if fixTrailingSlash && len(path) == k+1 { + return ciPath, true + } + return + } + } + + if n.handle != nil { + return ciPath, true + } else if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handle != nil { + return append(ciPath, '/'), true + } + } + return + + case catchAll: + return append(ciPath, path...), true + + default: + panic("Unknown node type") + } + } + } else { + // We should have reached the node containing the handle. + // Check if this node has a handle registered. + if n.handle != nil { + return ciPath, true + } + + // No handle found. + // Try to fix the path by adding a trailing slash + if fixTrailingSlash { + for i, index := range n.indices { + if index == '/' { + n = n.children[i] + if (n.path == "/" && n.handle != nil) || + (n.nType == catchAll && n.children[0].handle != nil) { + return append(ciPath, '/'), true + } + return + } + } + } + return + } + } + + // Nothing found. + // Try to fix the path by adding / removing a trailing slash + if fixTrailingSlash { + if path == "/" { + return ciPath, true + } + if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && + strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && + n.handle != nil { + return append(ciPath, n.path...), true + } + } + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree_test.go new file mode 100644 index 00000000..cf4d1708 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/julienschmidt/httprouter/tree_test.go @@ -0,0 +1,559 @@ +// Copyright 2013 Julien Schmidt. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +package httprouter + +import ( + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func printChildren(n *node, prefix string) { + fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType) + for l := len(n.path); l > 0; l-- { + prefix += " " + } + for _, child := range n.children { + printChildren(child, prefix) + } +} + +// Used as a workaround since we can't compare functions or their adresses +var fakeHandlerValue string + +func fakeHandler(val string) Handle { + return func(http.ResponseWriter, *http.Request, Params) { + fakeHandlerValue = val + } +} + +type testRequests []struct { + path string + nilHandler bool + route string + ps Params +} + +func checkRequests(t *testing.T, tree *node, requests testRequests) { + for _, request := range requests { + handler, ps, _ := tree.getValue(request.path) + + if handler == nil { + if !request.nilHandler { + t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) + } + } else if request.nilHandler { + t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) + } else { + handler(nil, nil, nil) + if fakeHandlerValue != request.route { + t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) + } + } + + if !reflect.DeepEqual(ps, request.ps) { + t.Errorf("Params mismatch for route '%s'", request.path) + } + } +} + +func checkPriorities(t *testing.T, n *node) uint32 { + var prio uint32 + for i := range n.children { + prio += checkPriorities(t, n.children[i]) + } + + if n.handle != nil { + prio++ + } + + if n.priority != prio { + t.Errorf( + "priority mismatch for node '%s': is %d, should be %d", + n.path, n.priority, prio, + ) + } + + return prio +} + +func checkMaxParams(t *testing.T, n *node) uint8 { + var maxParams uint8 + for i := range n.children { + params := checkMaxParams(t, n.children[i]) + if params > maxParams { + maxParams = params + } + } + if n.nType != static && !n.wildChild { + maxParams++ + } + + if n.maxParams != maxParams { + t.Errorf( + "maxParams mismatch for node '%s': is %d, should be %d", + n.path, n.maxParams, maxParams, + ) + } + + return maxParams +} + +func TestCountParams(t *testing.T) { + if countParams("/path/:param1/static/*catch-all") != 2 { + t.Fail() + } + if countParams(strings.Repeat("/:param", 256)) != 255 { + t.Fail() + } +} + +func TestTreeAddAndGet(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/hi", + "/contact", + "/co", + "/c", + "/a", + "/ab", + "/doc/", + "/doc/go_faq.html", + "/doc/go1.html", + } + for _, route := range routes { + tree.addRoute(route, fakeHandler(route)) + } + + //printChildren(tree, "") + + checkRequests(t, tree, testRequests{ + {"/a", false, "/a", nil}, + {"/", true, "", nil}, + {"/hi", false, "/hi", nil}, + {"/contact", false, "/contact", nil}, + {"/co", false, "/co", nil}, + {"/con", true, "", nil}, // key mismatch + {"/cona", true, "", nil}, // key mismatch + {"/no", true, "", nil}, // no matching child + {"/ab", false, "/ab", nil}, + }) + + checkPriorities(t, tree) + checkMaxParams(t, tree) +} + +func TestTreeWildcard(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/", + "/cmd/:tool/:sub", + "/cmd/:tool/", + "/src/*filepath", + "/search/", + "/search/:query", + "/user_:name", + "/user_:name/about", + "/files/:dir/*filepath", + "/doc/", + "/doc/go_faq.html", + "/doc/go1.html", + "/info/:user/public", + "/info/:user/project/:project", + } + for _, route := range routes { + tree.addRoute(route, fakeHandler(route)) + } + + //printChildren(tree, "") + + checkRequests(t, tree, testRequests{ + {"/", false, "/", nil}, + {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, + {"/cmd/test", true, "", Params{Param{"tool", "test"}}}, + {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}}, + {"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, + {"/search/", false, "/search/", nil}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, + {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, + {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, + {"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}}, + {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}}, + {"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}}, + {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}}, + }) + + checkPriorities(t, tree) + checkMaxParams(t, tree) +} + +func catchPanic(testFunc func()) (recv interface{}) { + defer func() { + recv = recover() + }() + + testFunc() + return +} + +type testRoute struct { + path string + conflict bool +} + +func testRoutes(t *testing.T, routes []testRoute) { + tree := &node{} + + for _, route := range routes { + recv := catchPanic(func() { + tree.addRoute(route.path, nil) + }) + + if route.conflict { + if recv == nil { + t.Errorf("no panic for conflicting route '%s'", route.path) + } + } else if recv != nil { + t.Errorf("unexpected panic for route '%s': %v", route.path, recv) + } + } + + //printChildren(tree, "") +} + +func TestTreeWildcardConflict(t *testing.T) { + routes := []testRoute{ + {"/cmd/:tool/:sub", false}, + {"/cmd/vet", true}, + {"/src/*filepath", false}, + {"/src/*filepathx", true}, + {"/src/", true}, + {"/src1/", false}, + {"/src1/*filepath", true}, + {"/src2*filepath", true}, + {"/search/:query", false}, + {"/search/invalid", true}, + {"/user_:name", false}, + {"/user_x", true}, + {"/user_:name", false}, + {"/id:id", false}, + {"/id/:id", true}, + } + testRoutes(t, routes) +} + +func TestTreeChildConflict(t *testing.T) { + routes := []testRoute{ + {"/cmd/vet", false}, + {"/cmd/:tool/:sub", true}, + {"/src/AUTHORS", false}, + {"/src/*filepath", true}, + {"/user_x", false}, + {"/user_:name", true}, + {"/id/:id", false}, + {"/id:id", true}, + {"/:id", true}, + {"/*filepath", true}, + } + testRoutes(t, routes) +} + +func TestTreeDupliatePath(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/", + "/doc/", + "/src/*filepath", + "/search/:query", + "/user_:name", + } + for _, route := range routes { + recv := catchPanic(func() { + tree.addRoute(route, fakeHandler(route)) + }) + if recv != nil { + t.Fatalf("panic inserting route '%s': %v", route, recv) + } + + // Add again + recv = catchPanic(func() { + tree.addRoute(route, nil) + }) + if recv == nil { + t.Fatalf("no panic while inserting duplicate route '%s", route) + } + } + + //printChildren(tree, "") + + checkRequests(t, tree, testRequests{ + {"/", false, "/", nil}, + {"/doc/", false, "/doc/", nil}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, + {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, + }) +} + +func TestEmptyWildcardName(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/user:", + "/user:/", + "/cmd/:/", + "/src/*", + } + for _, route := range routes { + recv := catchPanic(func() { + tree.addRoute(route, nil) + }) + if recv == nil { + t.Fatalf("no panic while inserting route with empty wildcard name '%s", route) + } + } +} + +func TestTreeCatchAllConflict(t *testing.T) { + routes := []testRoute{ + {"/src/*filepath/x", true}, + {"/src2/", false}, + {"/src2/*filepath/x", true}, + } + testRoutes(t, routes) +} + +func TestTreeCatchAllConflictRoot(t *testing.T) { + routes := []testRoute{ + {"/", false}, + {"/*filepath", true}, + } + testRoutes(t, routes) +} + +/*func TestTreeDuplicateWildcard(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/:id/:name/:id", + } + for _, route := range routes { + ... + } +}*/ + +func TestTreeTrailingSlashRedirect(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/hi", + "/b/", + "/search/:query", + "/cmd/:tool/", + "/src/*filepath", + "/x", + "/x/y", + "/y/", + "/y/z", + "/0/:id", + "/0/:id/1", + "/1/:id/", + "/1/:id/2", + "/aa", + "/a/", + "/doc", + "/doc/go_faq.html", + "/doc/go1.html", + "/no/a", + "/no/b", + "/api/hello/:name", + } + for _, route := range routes { + recv := catchPanic(func() { + tree.addRoute(route, fakeHandler(route)) + }) + if recv != nil { + t.Fatalf("panic inserting route '%s': %v", route, recv) + } + } + + //printChildren(tree, "") + + tsrRoutes := [...]string{ + "/hi/", + "/b", + "/search/gopher/", + "/cmd/vet", + "/src", + "/x/", + "/y", + "/0/go/", + "/1/go", + "/a", + "/doc/", + } + for _, route := range tsrRoutes { + handler, _, tsr := tree.getValue(route) + if handler != nil { + t.Fatalf("non-nil handler for TSR route '%s", route) + } else if !tsr { + t.Errorf("expected TSR recommendation for route '%s'", route) + } + } + + noTsrRoutes := [...]string{ + "/", + "/no", + "/no/", + "/_", + "/_/", + "/api/world/abc", + } + for _, route := range noTsrRoutes { + handler, _, tsr := tree.getValue(route) + if handler != nil { + t.Fatalf("non-nil handler for No-TSR route '%s", route) + } else if tsr { + t.Errorf("expected no TSR recommendation for route '%s'", route) + } + } +} + +func TestTreeFindCaseInsensitivePath(t *testing.T) { + tree := &node{} + + routes := [...]string{ + "/hi", + "/b/", + "/ABC/", + "/search/:query", + "/cmd/:tool/", + "/src/*filepath", + "/x", + "/x/y", + "/y/", + "/y/z", + "/0/:id", + "/0/:id/1", + "/1/:id/", + "/1/:id/2", + "/aa", + "/a/", + "/doc", + "/doc/go_faq.html", + "/doc/go1.html", + "/doc/go/away", + "/no/a", + "/no/b", + } + + for _, route := range routes { + recv := catchPanic(func() { + tree.addRoute(route, fakeHandler(route)) + }) + if recv != nil { + t.Fatalf("panic inserting route '%s': %v", route, recv) + } + } + + // Check out == in for all registered routes + // With fixTrailingSlash = true + for _, route := range routes { + out, found := tree.findCaseInsensitivePath(route, true) + if !found { + t.Errorf("Route '%s' not found!", route) + } else if string(out) != route { + t.Errorf("Wrong result for route '%s': %s", route, string(out)) + } + } + // With fixTrailingSlash = false + for _, route := range routes { + out, found := tree.findCaseInsensitivePath(route, false) + if !found { + t.Errorf("Route '%s' not found!", route) + } else if string(out) != route { + t.Errorf("Wrong result for route '%s': %s", route, string(out)) + } + } + + tests := []struct { + in string + out string + found bool + slash bool + }{ + {"/HI", "/hi", true, false}, + {"/HI/", "/hi", true, true}, + {"/B", "/b/", true, true}, + {"/B/", "/b/", true, false}, + {"/abc", "/ABC/", true, true}, + {"/abc/", "/ABC/", true, false}, + {"/aBc", "/ABC/", true, true}, + {"/aBc/", "/ABC/", true, false}, + {"/abC", "/ABC/", true, true}, + {"/abC/", "/ABC/", true, false}, + {"/SEARCH/QUERY", "/search/QUERY", true, false}, + {"/SEARCH/QUERY/", "/search/QUERY", true, true}, + {"/CMD/TOOL/", "/cmd/TOOL/", true, false}, + {"/CMD/TOOL", "/cmd/TOOL/", true, true}, + {"/SRC/FILE/PATH", "/src/FILE/PATH", true, false}, + {"/x/Y", "/x/y", true, false}, + {"/x/Y/", "/x/y", true, true}, + {"/X/y", "/x/y", true, false}, + {"/X/y/", "/x/y", true, true}, + {"/X/Y", "/x/y", true, false}, + {"/X/Y/", "/x/y", true, true}, + {"/Y/", "/y/", true, false}, + {"/Y", "/y/", true, true}, + {"/Y/z", "/y/z", true, false}, + {"/Y/z/", "/y/z", true, true}, + {"/Y/Z", "/y/z", true, false}, + {"/Y/Z/", "/y/z", true, true}, + {"/y/Z", "/y/z", true, false}, + {"/y/Z/", "/y/z", true, true}, + {"/Aa", "/aa", true, false}, + {"/Aa/", "/aa", true, true}, + {"/AA", "/aa", true, false}, + {"/AA/", "/aa", true, true}, + {"/aA", "/aa", true, false}, + {"/aA/", "/aa", true, true}, + {"/A/", "/a/", true, false}, + {"/A", "/a/", true, true}, + {"/DOC", "/doc", true, false}, + {"/DOC/", "/doc", true, true}, + {"/NO", "", false, true}, + {"/DOC/GO", "", false, true}, + } + // With fixTrailingSlash = true + for _, test := range tests { + out, found := tree.findCaseInsensitivePath(test.in, true) + if found != test.found || (found && (string(out) != test.out)) { + t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", + test.in, string(out), found, test.out, test.found) + return + } + } + // With fixTrailingSlash = false + for _, test := range tests { + out, found := tree.findCaseInsensitivePath(test.in, false) + if test.slash { + if found { // test needs a trailingSlash fix. It must not be found! + t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out)) + } + } else { + if found != test.found || (found && (string(out) != test.out)) { + t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t", + test.in, string(out), found, test.out, test.found) + return + } + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/README.md new file mode 100644 index 00000000..c69da4a7 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/README.md @@ -0,0 +1,42 @@ +# go-colorable + +Colorable writer for windows. + +For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) +This package is possible to handle escape sequence for ansi color on windows. + +## Too Bad! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) + + +## So Good! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) + +## Usage + +```go +logrus.SetOutput(colorable.NewColorableStdout()) + +logrus.Info("succeeded") +logrus.Warn("not correct") +logrus.Error("something error") +logrus.Fatal("panic") +``` + +You can compile above code on non-windows OSs. + +## Installation + +``` +$ go get github.com/mattn/go-colorable +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/_example/main.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/_example/main.go new file mode 100644 index 00000000..f4115046 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/_example/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mattn/go-colorable" + "github.com/Sirupsen/logrus" +) + +func main() { + logrus.SetOutput(colorable.NewColorableStdout()) + + logrus.Info("succeeded") + logrus.Warn("not correct") + logrus.Error("something error") + logrus.Fatal("panic") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_others.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_others.go new file mode 100644 index 00000000..219f02f6 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,16 @@ +// +build !windows + +package colorable + +import ( + "io" + "os" +) + +func NewColorableStdout() io.Writer { + return os.Stdout +} + +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_windows.go b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_windows.go new file mode 100644 index 00000000..6a278780 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,594 @@ +package colorable + +import ( + "bytes" + "fmt" + "io" + "os" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/mattn/go-isatty" +) + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") +) + +type Writer struct { + out io.Writer + handle syscall.Handle + lastbuf bytes.Buffer + oldattr word +} + +func NewColorableStdout() io.Writer { + var csbi consoleScreenBufferInfo + out := os.Stdout + if !isatty.IsTerminal(out.Fd()) { + return out + } + handle := syscall.Handle(out.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: out, handle: handle, oldattr: csbi.attributes} +} + +func NewColorableStderr() io.Writer { + var csbi consoleScreenBufferInfo + out := os.Stderr + if !isatty.IsTerminal(out.Fd()) { + return out + } + handle := syscall.Handle(out.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: out, handle: handle, oldattr: csbi.attributes} +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewBuffer(data) +loop: + for { + r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if r1 == 0 { + break loop + } + + c1, _, err := er.ReadRune() + if err != nil { + break loop + } + if c1 != 0x1b { + fmt.Fprint(w.out, string(c1)) + continue + } + c2, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + break loop + } + if c2 != 0x5b { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + continue + } + + var buf bytes.Buffer + var m rune + for { + c, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + w.lastbuf.Write(buf.Bytes()) + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + switch m { + case 'm': + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i, ns := range token { + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 22 == n || n == 25 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr = (attr & backgroundMask) + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr = (attr & foregroundMask) + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + } + } + return len(data) - w.lastbuf.Len(), nil +} + +type consoleColor struct { + red bool + green bool + blue bool + intensity bool +} + +func minmax3(a, b, c int) (min, max int) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +func toConsoleColor(rgb int) (c consoleColor) { + r, g, b := (rgb&0xFF0000)>>16, (rgb&0x00FF00)>>8, rgb&0x0000FF + min, max := minmax3(r, g, b) + a := (min + max) / 2 + if r < 128 && g < 128 && b < 128 { + if r >= a { + c.red = true + } + if g >= a { + c.green = true + } + if b >= a { + c.blue = true + } + // non-intensed white is lighter than intensed black, so swap those. + if c.red && c.green && c.blue { + c.red, c.green, c.blue = false, false, false + c.intensity = true + } + } else { + if min < 128 { + min = 128 + a = (min + max) / 2 + } + if r >= a { + c.red = true + } + if g >= a { + c.green = true + } + if b >= a { + c.blue = true + } + c.intensity = true + // intensed black is darker than non-intensed white, so swap those. + if !c.red && !c.green && !c.blue { + c.red, c.green, c.blue = true, true, true + c.intensity = false + } + } + return c +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + for i, rgb := range color256 { + c := toConsoleColor(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.gitignore b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.gitignore new file mode 100644 index 00000000..5cdbab79 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.gitignore @@ -0,0 +1,4 @@ +*~ +*.pyc +test-env* +junk/ \ No newline at end of file diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.travis.yml b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.travis.yml new file mode 100644 index 00000000..391b1d19 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.1.2 + - 1.2.2 + - 1.3 + - tip + +script: + - go test diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/COPYING b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/COPYING new file mode 100644 index 00000000..8c27c67f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/COPYING @@ -0,0 +1,20 @@ +Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/README.md b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/README.md new file mode 100644 index 00000000..039a74a7 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/README.md @@ -0,0 +1,105 @@ +Swift +===== + +This package provides an easy to use library for interfacing with +Swift / Openstack Object Storage / Rackspace cloud files from the Go +Language + +See here for package docs + + http://godoc.org/github.com/ncw/swift + +[![Build Status](https://travis-ci.org/ncw/swift.png)](https://travis-ci.org/ncw/swift) + +Install +------- + +Use go to install the library + + go get github.com/ncw/swift + +Usage +----- + +See here for full package docs + +- http://godoc.org/github.com/ncw/swift + +Here is a short example from the docs + + import "github.com/ncw/swift" + + // Create a connection + c := swift.Connection{ + UserName: "user", + ApiKey: "key", + AuthUrl: "auth_url", + } + // Authenticate + err := c.Authenticate() + if err != nil { + panic(err) + } + // List all the containers + containers, err := c.ContainerNames(nil) + fmt.Println(containers) + // etc... + +Additions +--------- + +The `rs` sub project contains a wrapper for the Rackspace specific CDN Management interface. + +Testing +------- + +To run the tests you can either use an embedded fake Swift server +either use a real Openstack Swift server or a Rackspace Cloud files account. + +When using a real Swift server, you need to set these environment variables +before running the tests + + export SWIFT_API_USER='user' + export SWIFT_API_KEY='key' + export SWIFT_AUTH_URL='https://url.of.auth.server/v1.0' + +And optionally these if using v2 authentication + + export SWIFT_TENANT='TenantName' + export SWIFT_TENANT_ID='TenantId' + +Then run the tests with `go test` + +License +------- + +This is free software under the terms of MIT license (check COPYING file +included in this package). + +Contact and support +------------------- + +The project website is at: + +- https://github.com/ncw/swift + +There you can file bug reports, ask for help or contribute patches. + +Authors +------- + +- Nick Craig-Wood + +Contributors +------------ + +- Brian "bojo" Jones +- Janika Liiv +- Yamamoto, Hirotaka +- Stephen +- platformpurple +- Paul Querna +- Livio Soares +- thesyncim +- lsowen +- Sylvain Baubeau diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/auth.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/auth.go new file mode 100644 index 00000000..8d2f54ea --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/auth.go @@ -0,0 +1,279 @@ +package swift + +import ( + "bytes" + "encoding/json" + "net/http" + "net/url" + "strings" +) + +// Auth defines the operations needed to authenticate with swift +// +// This encapsulates the different authentication schemes in use +type Authenticator interface { + Request(*Connection) (*http.Request, error) + Response(resp *http.Response) error + // The public storage URL - set Internal to true to read + // internal/service net URL + StorageUrl(Internal bool) string + // The access token + Token() string + // The CDN url if available + CdnUrl() string +} + +// newAuth - create a new Authenticator from the AuthUrl +// +// A hint for AuthVersion can be provided +func newAuth(c *Connection) (Authenticator, error) { + AuthVersion := c.AuthVersion + if AuthVersion == 0 { + if strings.Contains(c.AuthUrl, "v2") { + AuthVersion = 2 + } else if strings.Contains(c.AuthUrl, "v1") { + AuthVersion = 1 + } else { + return nil, newErrorf(500, "Can't find AuthVersion in AuthUrl - set explicitly") + } + } + switch AuthVersion { + case 1: + return &v1Auth{}, nil + case 2: + return &v2Auth{ + // Guess as to whether using API key or + // password it will try both eventually so + // this is just an optimization. + useApiKey: len(c.ApiKey) >= 32, + }, nil + } + return nil, newErrorf(500, "Auth Version %d not supported", AuthVersion) +} + +// ------------------------------------------------------------ + +// v1 auth +type v1Auth struct { + Headers http.Header // V1 auth: the authentication headers so extensions can access them +} + +// v1 Authentication - make request +func (auth *v1Auth) Request(c *Connection) (*http.Request, error) { + req, err := http.NewRequest("GET", c.AuthUrl, nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", c.UserAgent) + req.Header.Set("X-Auth-Key", c.ApiKey) + req.Header.Set("X-Auth-User", c.UserName) + return req, nil +} + +// v1 Authentication - read response +func (auth *v1Auth) Response(resp *http.Response) error { + auth.Headers = resp.Header + return nil +} + +// v1 Authentication - read storage url +func (auth *v1Auth) StorageUrl(Internal bool) string { + storageUrl := auth.Headers.Get("X-Storage-Url") + if Internal { + newUrl, err := url.Parse(storageUrl) + if err != nil { + return storageUrl + } + newUrl.Host = "snet-" + newUrl.Host + storageUrl = newUrl.String() + } + return storageUrl +} + +// v1 Authentication - read auth token +func (auth *v1Auth) Token() string { + return auth.Headers.Get("X-Auth-Token") +} + +// v1 Authentication - read cdn url +func (auth *v1Auth) CdnUrl() string { + return auth.Headers.Get("X-CDN-Management-Url") +} + +// ------------------------------------------------------------ + +// v2 Authentication +type v2Auth struct { + Auth *v2AuthResponse + Region string + useApiKey bool // if set will use API key not Password + useApiKeyOk bool // if set won't change useApiKey any more + notFirst bool // set after first run +} + +// v2 Authentication - make request +func (auth *v2Auth) Request(c *Connection) (*http.Request, error) { + auth.Region = c.Region + // Toggle useApiKey if not first run and not OK yet + if auth.notFirst && !auth.useApiKeyOk { + auth.useApiKey = !auth.useApiKey + } + auth.notFirst = true + // Create a V2 auth request for the body of the connection + var v2i interface{} + if !auth.useApiKey { + // Normal swift authentication + v2 := v2AuthRequest{} + v2.Auth.PasswordCredentials.UserName = c.UserName + v2.Auth.PasswordCredentials.Password = c.ApiKey + v2.Auth.Tenant = c.Tenant + v2.Auth.TenantId = c.TenantId + v2i = v2 + } else { + // Rackspace special with API Key + v2 := v2AuthRequestRackspace{} + v2.Auth.ApiKeyCredentials.UserName = c.UserName + v2.Auth.ApiKeyCredentials.ApiKey = c.ApiKey + v2.Auth.Tenant = c.Tenant + v2.Auth.TenantId = c.TenantId + v2i = v2 + } + body, err := json.Marshal(v2i) + if err != nil { + return nil, err + } + url := c.AuthUrl + if !strings.HasSuffix(url, "/") { + url += "/" + } + url += "tokens" + req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + return req, nil +} + +// v2 Authentication - read response +func (auth *v2Auth) Response(resp *http.Response) error { + auth.Auth = new(v2AuthResponse) + err := readJson(resp, auth.Auth) + // If successfully read Auth then no need to toggle useApiKey any more + if err == nil { + auth.useApiKeyOk = true + } + return err +} + +// Finds the Endpoint Url of "type" from the v2AuthResponse using the +// Region if set or defaulting to the first one if not +// +// Returns "" if not found +func (auth *v2Auth) endpointUrl(Type string, Internal bool) string { + for _, catalog := range auth.Auth.Access.ServiceCatalog { + if catalog.Type == Type { + for _, endpoint := range catalog.Endpoints { + if auth.Region == "" || (auth.Region == endpoint.Region) { + if Internal { + return endpoint.InternalUrl + } else { + return endpoint.PublicUrl + } + } + } + } + } + return "" +} + +// v2 Authentication - read storage url +// +// If Internal is true then it reads the private (internal / service +// net) URL. +func (auth *v2Auth) StorageUrl(Internal bool) string { + return auth.endpointUrl("object-store", Internal) +} + +// v2 Authentication - read auth token +func (auth *v2Auth) Token() string { + return auth.Auth.Access.Token.Id +} + +// v2 Authentication - read cdn url +func (auth *v2Auth) CdnUrl() string { + return auth.endpointUrl("rax:object-cdn", false) +} + +// ------------------------------------------------------------ + +// V2 Authentication request +// +// http://docs.openstack.org/developer/keystone/api_curl_examples.html +// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html +// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html +type v2AuthRequest struct { + Auth struct { + PasswordCredentials struct { + UserName string `json:"username"` + Password string `json:"password"` + } `json:"passwordCredentials"` + Tenant string `json:"tenantName,omitempty"` + TenantId string `json:"tenantId,omitempty"` + } `json:"auth"` +} + +// V2 Authentication request - Rackspace variant +// +// http://docs.openstack.org/developer/keystone/api_curl_examples.html +// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html +// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html +type v2AuthRequestRackspace struct { + Auth struct { + ApiKeyCredentials struct { + UserName string `json:"username"` + ApiKey string `json:"apiKey"` + } `json:"RAX-KSKEY:apiKeyCredentials"` + Tenant string `json:"tenantName,omitempty"` + TenantId string `json:"tenantId,omitempty"` + } `json:"auth"` +} + +// V2 Authentication reply +// +// http://docs.openstack.org/developer/keystone/api_curl_examples.html +// http://docs.rackspace.com/servers/api/v2/cs-gettingstarted/content/curl_auth.html +// http://docs.openstack.org/api/openstack-identity-service/2.0/content/POST_authenticate_v2.0_tokens_.html +type v2AuthResponse struct { + Access struct { + ServiceCatalog []struct { + Endpoints []struct { + InternalUrl string + PublicUrl string + Region string + TenantId string + } + Name string + Type string + } + Token struct { + Expires string + Id string + Tenant struct { + Id string + Name string + } + } + User struct { + DefaultRegion string `json:"RAX-AUTH:defaultRegion"` + Id string + Name string + Roles []struct { + Description string + Id string + Name string + TenantId string + } + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_0.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_0.go new file mode 100644 index 00000000..7b69a757 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_0.go @@ -0,0 +1,28 @@ +// Go 1.0 compatibility functions + +// +build !go1.1 + +package swift + +import ( + "log" + "net/http" + "time" +) + +// Cancel the request - doesn't work under < go 1.1 +func cancelRequest(transport http.RoundTripper, req *http.Request) { + log.Printf("Tried to cancel a request but couldn't - recompile with go 1.1") +} + +// Reset a timer - Doesn't work properly < go 1.1 +// +// This is quite hard to do properly under go < 1.1 so we do a crude +// approximation and hope that everyone upgrades to go 1.1 quickly +func resetTimer(t *time.Timer, d time.Duration) { + t.Stop() + // Very likely this doesn't actually work if we are already + // selecting on t.C. However we've stopped the original timer + // so won't break transfers but may not time them out :-( + *t = *time.NewTimer(d) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_1.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_1.go new file mode 100644 index 00000000..a4f9c3ab --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/compatibility_1_1.go @@ -0,0 +1,24 @@ +// Go 1.1 and later compatibility functions +// +// +build go1.1 + +package swift + +import ( + "net/http" + "time" +) + +// Cancel the request +func cancelRequest(transport http.RoundTripper, req *http.Request) { + if tr, ok := transport.(interface { + CancelRequest(*http.Request) + }); ok { + tr.CancelRequest(req) + } +} + +// Reset a timer +func resetTimer(t *time.Timer, d time.Duration) { + t.Reset(d) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/doc.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/doc.go new file mode 100644 index 00000000..44efde7b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/doc.go @@ -0,0 +1,19 @@ +/* +Package swift provides an easy to use interface to Swift / Openstack Object Storage / Rackspace Cloud Files + +Standard Usage + +Most of the work is done through the Container*() and Object*() methods. + +All methods are safe to use concurrently in multiple go routines. + +Object Versioning + +As defined by http://docs.openstack.org/api/openstack-object-storage/1.0/content/Object_Versioning-e1e3230.html#d6e983 one can create a container which allows for version control of files. The suggested method is to create a version container for holding all non-current files, and a current container for holding the latest version that the file points to. The container and objects inside it can be used in the standard manner, however, pushing a file multiple times will result in it being copied to the version container and the new file put in it's place. If the current file is deleted, the previous file in the version container will replace it. This means that if a file is updated 5 times, it must be deleted 5 times to be completely removed from the system. + +Rackspace Sub Module + +This module specifically allows the enabling/disabling of Rackspace Cloud File CDN management on a container. This is specific to the Rackspace API and not Swift/Openstack, therefore it has been placed in a submodule. One can easily create a RsConnection and use it like the standard Connection to access and manipulate containers and objects. + +*/ +package swift diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/example_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/example_test.go new file mode 100644 index 00000000..30cb9834 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/example_test.go @@ -0,0 +1,97 @@ +// Copyright... + +// This example demonstrates opening a Connection and doing some basic operations. +package swift_test + +import ( + "fmt" + + "github.com/ncw/swift" +) + +func ExampleConnection() { + // Create a v1 auth connection + c := swift.Connection{ + // This should be your username + UserName: "user", + // This should be your api key + ApiKey: "key", + // This should be a v1 auth url, eg + // Rackspace US https://auth.api.rackspacecloud.com/v1.0 + // Rackspace UK https://lon.auth.api.rackspacecloud.com/v1.0 + // Memset Memstore UK https://auth.storage.memset.com/v1.0 + AuthUrl: "auth_url", + } + + // Authenticate + err := c.Authenticate() + if err != nil { + panic(err) + } + // List all the containers + containers, err := c.ContainerNames(nil) + fmt.Println(containers) + // etc... + + // ------ or alternatively create a v2 connection ------ + + // Create a v2 auth connection + c = swift.Connection{ + // This is the sub user for the storage - eg "admin" + UserName: "user", + // This should be your api key + ApiKey: "key", + // This should be a version2 auth url, eg + // Rackspace v2 https://identity.api.rackspacecloud.com/v2.0 + // Memset Memstore v2 https://auth.storage.memset.com/v2.0 + AuthUrl: "v2_auth_url", + // Region to use - default is use first region if unset + Region: "LON", + // Name of the tenant - this is likely your username + Tenant: "jim", + } + + // as above... +} + +var container string + +func ExampleConnection_ObjectsWalk() { + objects := make([]string, 0) + err := c.ObjectsWalk(container, nil, func(opts *swift.ObjectsOpts) (interface{}, error) { + newObjects, err := c.ObjectNames(container, opts) + if err == nil { + objects = append(objects, newObjects...) + } + return newObjects, err + }) + fmt.Println("Found all the objects", objects, err) +} + +func ExampleConnection_VersionContainerCreate() { + // Use the helper method to create the current and versions container. + if err := c.VersionContainerCreate("cds", "cd-versions"); err != nil { + fmt.Print(err.Error()) + } +} + +func ExampleConnection_VersionEnable() { + // Build the containers manually and enable them. + if err := c.ContainerCreate("movie-versions", nil); err != nil { + fmt.Print(err.Error()) + } + if err := c.ContainerCreate("movies", nil); err != nil { + fmt.Print(err.Error()) + } + if err := c.VersionEnable("movies", "movie-versions"); err != nil { + fmt.Print(err.Error()) + } + + // Access the primary container as usual with ObjectCreate(), ObjectPut(), etc. + // etc... +} + +func ExampleConnection_VersionDisable() { + // Disable versioning on a container. Note that this does not delete the versioning container. + c.VersionDisable("movies") +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta.go new file mode 100644 index 00000000..e52d6860 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta.go @@ -0,0 +1,174 @@ +// Metadata manipulation in and out of Headers + +package swift + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +// Metadata stores account, container or object metadata. +type Metadata map[string]string + +// Metadata gets the Metadata starting with the metaPrefix out of the Headers. +// +// The keys in the Metadata will be converted to lower case +func (h Headers) Metadata(metaPrefix string) Metadata { + m := Metadata{} + metaPrefix = http.CanonicalHeaderKey(metaPrefix) + for key, value := range h { + if strings.HasPrefix(key, metaPrefix) { + metaKey := strings.ToLower(key[len(metaPrefix):]) + m[metaKey] = value + } + } + return m +} + +// AccountMetadata converts Headers from account to a Metadata. +// +// The keys in the Metadata will be converted to lower case. +func (h Headers) AccountMetadata() Metadata { + return h.Metadata("X-Account-Meta-") +} + +// ContainerMetadata converts Headers from container to a Metadata. +// +// The keys in the Metadata will be converted to lower case. +func (h Headers) ContainerMetadata() Metadata { + return h.Metadata("X-Container-Meta-") +} + +// ObjectMetadata converts Headers from object to a Metadata. +// +// The keys in the Metadata will be converted to lower case. +func (h Headers) ObjectMetadata() Metadata { + return h.Metadata("X-Object-Meta-") +} + +// Headers convert the Metadata starting with the metaPrefix into a +// Headers. +// +// The keys in the Metadata will be converted from lower case to http +// Canonical (see http.CanonicalHeaderKey). +func (m Metadata) Headers(metaPrefix string) Headers { + h := Headers{} + for key, value := range m { + key = http.CanonicalHeaderKey(metaPrefix + key) + h[key] = value + } + return h +} + +// AccountHeaders converts the Metadata for the account. +func (m Metadata) AccountHeaders() Headers { + return m.Headers("X-Account-Meta-") +} + +// ContainerHeaders converts the Metadata for the container. +func (m Metadata) ContainerHeaders() Headers { + return m.Headers("X-Container-Meta-") +} + +// ObjectHeaders converts the Metadata for the object. +func (m Metadata) ObjectHeaders() Headers { + return m.Headers("X-Object-Meta-") +} + +// Turns a number of ns into a floating point string in seconds +// +// Trims trailing zeros and guaranteed to be perfectly accurate +func nsToFloatString(ns int64) string { + if ns < 0 { + return "-" + nsToFloatString(-ns) + } + result := fmt.Sprintf("%010d", ns) + split := len(result) - 9 + result, decimals := result[:split], result[split:] + decimals = strings.TrimRight(decimals, "0") + if decimals != "" { + result += "." + result += decimals + } + return result +} + +// Turns a floating point string in seconds into a ns integer +// +// Guaranteed to be perfectly accurate +func floatStringToNs(s string) (int64, error) { + const zeros = "000000000" + if point := strings.IndexRune(s, '.'); point >= 0 { + tail := s[point+1:] + if fill := 9 - len(tail); fill < 0 { + tail = tail[:9] + } else { + tail += zeros[:fill] + } + s = s[:point] + tail + } else if len(s) > 0 { // Make sure empty string produces an error + s += zeros + } + return strconv.ParseInt(s, 10, 64) +} + +// FloatStringToTime converts a floating point number string to a time.Time +// +// The string is floating point number of seconds since the epoch +// (Unix time). The number should be in fixed point format (not +// exponential), eg "1354040105.123456789" which represents the time +// "2012-11-27T18:15:05.123456789Z" +// +// Some care is taken to preserve all the accuracy in the time.Time +// (which wouldn't happen with a naive conversion through float64) so +// a round trip conversion won't change the data. +// +// If an error is returned then time will be returned as the zero time. +func FloatStringToTime(s string) (t time.Time, err error) { + ns, err := floatStringToNs(s) + if err != nil { + return + } + t = time.Unix(0, ns) + return +} + +// TimeToFloatString converts a time.Time object to a floating point string +// +// The string is floating point number of seconds since the epoch +// (Unix time). The number is in fixed point format (not +// exponential), eg "1354040105.123456789" which represents the time +// "2012-11-27T18:15:05.123456789Z". Trailing zeros will be dropped +// from the output. +// +// Some care is taken to preserve all the accuracy in the time.Time +// (which wouldn't happen with a naive conversion through float64) so +// a round trip conversion won't change the data. +func TimeToFloatString(t time.Time) string { + return nsToFloatString(t.UnixNano()) +} + +// Read a modification time (mtime) from a Metadata object +// +// This is a defacto standard (used in the official python-swiftclient +// amongst others) for storing the modification time (as read using +// os.Stat) for an object. It is stored using the key 'mtime', which +// for example when written to an object will be 'X-Object-Meta-Mtime'. +// +// If an error is returned then time will be returned as the zero time. +func (m Metadata) GetModTime() (t time.Time, err error) { + return FloatStringToTime(m["mtime"]) +} + +// Write an modification time (mtime) to a Metadata object +// +// This is a defacto standard (used in the official python-swiftclient +// amongst others) for storing the modification time (as read using +// os.Stat) for an object. It is stored using the key 'mtime', which +// for example when written to an object will be 'X-Object-Meta-Mtime'. +func (m Metadata) SetModTime(t time.Time) { + m["mtime"] = TimeToFloatString(t) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta_test.go new file mode 100644 index 00000000..47560d57 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/meta_test.go @@ -0,0 +1,213 @@ +// Tests for swift metadata +package swift + +import ( + "testing" + "time" +) + +func TestHeadersToMetadata(t *testing.T) { +} + +func TestHeadersToAccountMetadata(t *testing.T) { +} + +func TestHeadersToContainerMetadata(t *testing.T) { +} + +func TestHeadersToObjectMetadata(t *testing.T) { +} + +func TestMetadataToHeaders(t *testing.T) { +} + +func TestMetadataToAccountHeaders(t *testing.T) { +} + +func TestMetadataToContainerHeaders(t *testing.T) { +} + +func TestMetadataToObjectHeaders(t *testing.T) { +} + +func TestNsToFloatString(t *testing.T) { + for _, d := range []struct { + ns int64 + fs string + }{ + {0, "0"}, + {1, "0.000000001"}, + {1000, "0.000001"}, + {1000000, "0.001"}, + {100000000, "0.1"}, + {1000000000, "1"}, + {10000000000, "10"}, + {12345678912, "12.345678912"}, + {12345678910, "12.34567891"}, + {12345678900, "12.3456789"}, + {12345678000, "12.345678"}, + {12345670000, "12.34567"}, + {12345600000, "12.3456"}, + {12345000000, "12.345"}, + {12340000000, "12.34"}, + {12300000000, "12.3"}, + {12000000000, "12"}, + {10000000000, "10"}, + {1347717491123123123, "1347717491.123123123"}, + } { + if nsToFloatString(d.ns) != d.fs { + t.Error("Failed", d.ns, "!=", d.fs) + } + if d.ns > 0 && nsToFloatString(-d.ns) != "-"+d.fs { + t.Error("Failed on negative", d.ns, "!=", d.fs) + } + } +} + +func TestFloatStringToNs(t *testing.T) { + for _, d := range []struct { + ns int64 + fs string + }{ + {0, "0"}, + {0, "0."}, + {0, ".0"}, + {0, "0.0"}, + {0, "0.0000000001"}, + {1, "0.000000001"}, + {1000, "0.000001"}, + {1000000, "0.001"}, + {100000000, "0.1"}, + {100000000, "0.10"}, + {100000000, "0.1000000001"}, + {1000000000, "1"}, + {1000000000, "1."}, + {1000000000, "1.0"}, + {10000000000, "10"}, + {12345678912, "12.345678912"}, + {12345678912, "12.3456789129"}, + {12345678912, "12.34567891299"}, + {12345678910, "12.34567891"}, + {12345678900, "12.3456789"}, + {12345678000, "12.345678"}, + {12345670000, "12.34567"}, + {12345600000, "12.3456"}, + {12345000000, "12.345"}, + {12340000000, "12.34"}, + {12300000000, "12.3"}, + {12000000000, "12"}, + {10000000000, "10"}, + // This is a typical value which has more bits in than a float64 + {1347717491123123123, "1347717491.123123123"}, + } { + ns, err := floatStringToNs(d.fs) + if err != nil { + t.Error("Failed conversion", err) + } + if ns != d.ns { + t.Error("Failed", d.fs, "!=", d.ns, "was", ns) + } + if d.ns > 0 { + ns, err := floatStringToNs("-" + d.fs) + if err != nil { + t.Error("Failed conversion", err) + } + if ns != -d.ns { + t.Error("Failed on negative", -d.ns, "!=", "-"+d.fs) + } + } + } + + // These are expected to produce errors + for _, fs := range []string{ + "", + " 1", + "- 1", + "- 1", + "1.-1", + "1.0.0", + "1x0", + } { + ns, err := floatStringToNs(fs) + if err == nil { + t.Error("Didn't produce expected error", fs, ns) + } + } + +} + +func TestGetModTime(t *testing.T) { + for _, d := range []struct { + ns string + t string + }{ + {"1354040105", "2012-11-27T18:15:05Z"}, + {"1354040105.", "2012-11-27T18:15:05Z"}, + {"1354040105.0", "2012-11-27T18:15:05Z"}, + {"1354040105.000000000000", "2012-11-27T18:15:05Z"}, + {"1354040105.123", "2012-11-27T18:15:05.123Z"}, + {"1354040105.123456", "2012-11-27T18:15:05.123456Z"}, + {"1354040105.123456789", "2012-11-27T18:15:05.123456789Z"}, + {"1354040105.123456789123", "2012-11-27T18:15:05.123456789Z"}, + {"0", "1970-01-01T00:00:00.000000000Z"}, + } { + expected, err := time.Parse(time.RFC3339, d.t) + if err != nil { + t.Error("Bad test", err) + } + m := Metadata{"mtime": d.ns} + actual, err := m.GetModTime() + if err != nil { + t.Error("Parse error", err) + } + if !actual.Equal(expected) { + t.Error("Expecting", expected, expected.UnixNano(), "got", actual, actual.UnixNano()) + } + } + for _, ns := range []string{ + "EMPTY", + "", + " 1", + "- 1", + "- 1", + "1.-1", + "1.0.0", + "1x0", + } { + m := Metadata{} + if ns != "EMPTY" { + m["mtime"] = ns + } + actual, err := m.GetModTime() + if err == nil { + t.Error("Expected error not produced") + } + if !actual.IsZero() { + t.Error("Expected output to be zero") + } + } +} + +func TestSetModTime(t *testing.T) { + for _, d := range []struct { + ns string + t string + }{ + {"1354040105", "2012-11-27T18:15:05Z"}, + {"1354040105", "2012-11-27T18:15:05.000000Z"}, + {"1354040105.123", "2012-11-27T18:15:05.123Z"}, + {"1354040105.123456", "2012-11-27T18:15:05.123456Z"}, + {"1354040105.123456789", "2012-11-27T18:15:05.123456789Z"}, + {"0", "1970-01-01T00:00:00.000000000Z"}, + } { + time, err := time.Parse(time.RFC3339, d.t) + if err != nil { + t.Error("Bad test", err) + } + m := Metadata{} + m.SetModTime(time) + if m["mtime"] != d.ns { + t.Error("mtime wrong", m, "should be", d.ns) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/notes.txt b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/notes.txt new file mode 100644 index 00000000..f738552c --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/notes.txt @@ -0,0 +1,55 @@ +Notes on Go Swift +================= + +Make a builder style interface like the Google Go APIs? Advantages +are that it is easy to add named methods to the service object to do +specific things. Slightly less efficient. Not sure about how to +return extra stuff though - in an object? + +Make a container struct so these could be methods on it? + +Make noResponse check for 204? + +Make storage public so it can be extended easily? + +Rename to go-swift to match user agent string? + +Reconnect on auth error - 401 when token expires isn't tested + +Make more api compatible with python cloudfiles? + +Retry operations on timeout / network errors? +- also 408 error +- GET requests only? + +Make Connection thread safe - whenever it is changed take a write lock whenever it is read from a read lock + +Add extra headers field to Connection (for via etc) + +Make errors use an error heirachy then can catch them with a type assertion + + Error(...) + ObjectCorrupted{ Error } + +Make a Debug flag in connection for logging stuff + +Object If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since etc + +Object range + +Object create, update with X-Delete-At or X-Delete-After + +Large object support +- check uploads are less than 5GB in normal mode? + +Access control CORS? + +Swift client retries and backs off for all types of errors + +Implement net error interface? + +type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs.go new file mode 100644 index 00000000..34ee15a0 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs.go @@ -0,0 +1,83 @@ +package rs + +import ( + "errors" + "net/http" + "strconv" + + "github.com/ncw/swift" +) + +// RsConnection is a RackSpace specific wrapper to the core swift library which +// exposes the RackSpace CDN commands via the CDN Management URL interface. +type RsConnection struct { + swift.Connection + cdnUrl string +} + +// manage is similar to the swift storage method, but uses the CDN Management URL for CDN specific calls. +func (c *RsConnection) manage(p swift.RequestOpts) (resp *http.Response, headers swift.Headers, err error) { + p.OnReAuth = func() (string, error) { + if c.cdnUrl == "" { + c.cdnUrl = c.Auth.CdnUrl() + } + if c.cdnUrl == "" { + return "", errors.New("The X-CDN-Management-Url does not exist on the authenticated platform") + } + return c.cdnUrl, nil + } + if c.Authenticated() { + _, err = p.OnReAuth() + if err != nil { + return nil, nil, err + } + } + return c.Connection.Call(c.cdnUrl, p) +} + +// ContainerCDNEnable enables a container for public CDN usage. +// +// Change the default TTL of 259200 seconds (72 hours) by passing in an integer value. +// +// This method can be called again to change the TTL. +func (c *RsConnection) ContainerCDNEnable(container string, ttl int) (swift.Headers, error) { + h := swift.Headers{"X-CDN-Enabled": "true"} + if ttl > 0 { + h["X-TTL"] = strconv.Itoa(ttl) + } + + _, headers, err := c.manage(swift.RequestOpts{ + Container: container, + Operation: "PUT", + ErrorMap: swift.ContainerErrorMap, + NoResponse: true, + Headers: h, + }) + return headers, err +} + +// ContainerCDNDisable disables CDN access to a container. +func (c *RsConnection) ContainerCDNDisable(container string) error { + h := swift.Headers{"X-CDN-Enabled": "false"} + + _, _, err := c.manage(swift.RequestOpts{ + Container: container, + Operation: "PUT", + ErrorMap: swift.ContainerErrorMap, + NoResponse: true, + Headers: h, + }) + return err +} + +// ContainerCDNMeta returns the CDN metadata for a container. +func (c *RsConnection) ContainerCDNMeta(container string) (swift.Headers, error) { + _, headers, err := c.manage(swift.RequestOpts{ + Container: container, + Operation: "HEAD", + ErrorMap: swift.ContainerErrorMap, + NoResponse: true, + Headers: swift.Headers{}, + }) + return headers, err +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs_test.go new file mode 100644 index 00000000..74205154 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/rs/rs_test.go @@ -0,0 +1,96 @@ +// See swift_test.go for requirements to run this test. +package rs_test + +import ( + "os" + "testing" + + "github.com/ncw/swift/rs" +) + +var ( + c rs.RsConnection +) + +const ( + CONTAINER = "GoSwiftUnitTest" + OBJECT = "test_object" + CONTENTS = "12345" + CONTENT_SIZE = int64(len(CONTENTS)) + CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b" +) + +// Test functions are run in order - this one must be first! +func TestAuthenticate(t *testing.T) { + UserName := os.Getenv("SWIFT_API_USER") + ApiKey := os.Getenv("SWIFT_API_KEY") + AuthUrl := os.Getenv("SWIFT_AUTH_URL") + if UserName == "" || ApiKey == "" || AuthUrl == "" { + t.Fatal("SWIFT_API_USER, SWIFT_API_KEY and SWIFT_AUTH_URL not all set") + } + c = rs.RsConnection{} + c.UserName = UserName + c.ApiKey = ApiKey + c.AuthUrl = AuthUrl + err := c.Authenticate() + if err != nil { + t.Fatal("Auth failed", err) + } + if !c.Authenticated() { + t.Fatal("Not authenticated") + } +} + +// Setup +func TestContainerCreate(t *testing.T) { + err := c.ContainerCreate(CONTAINER, nil) + if err != nil { + t.Fatal(err) + } +} + +func TestCDNEnable(t *testing.T) { + headers, err := c.ContainerCDNEnable(CONTAINER, 0) + if err != nil { + t.Error(err) + } + if _, ok := headers["X-Cdn-Uri"]; !ok { + t.Error("Failed to enable CDN for container") + } +} + +func TestOnReAuth(t *testing.T) { + c2 := rs.RsConnection{} + c2.UserName = c.UserName + c2.ApiKey = c.ApiKey + c2.AuthUrl = c.AuthUrl + _, err := c2.ContainerCDNEnable(CONTAINER, 0) + if err != nil { + t.Fatalf("Failed to reauthenticate: %v", err) + } +} + +func TestCDNMeta(t *testing.T) { + headers, err := c.ContainerCDNMeta(CONTAINER) + if err != nil { + t.Error(err) + } + if _, ok := headers["X-Cdn-Uri"]; !ok { + t.Error("CDN is not enabled") + } +} + +func TestCDNDisable(t *testing.T) { + err := c.ContainerCDNDisable(CONTAINER) // files stick in CDN until TTL expires + if err != nil { + t.Error(err) + } +} + +// Teardown +func TestContainerDelete(t *testing.T) { + err := c.ContainerDelete(CONTAINER) + if err != nil { + t.Fatal(err) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift.go new file mode 100644 index 00000000..1c0bf856 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift.go @@ -0,0 +1,1825 @@ +package swift + +import ( + "bufio" + "bytes" + "crypto/md5" + "encoding/json" + "fmt" + "hash" + "io" + "log" + "mime" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "sync" + "time" +) + +const ( + DefaultUserAgent = "goswift/1.0" // Default user agent + DefaultRetries = 3 // Default number of retries on token expiry + TimeFormat = "2006-01-02T15:04:05" // Python date format for json replies parsed as UTC + UploadTar = "tar" // Data format specifier for Connection.BulkUpload(). + UploadTarGzip = "tar.gz" // Data format specifier for Connection.BulkUpload(). + UploadTarBzip2 = "tar.bz2" // Data format specifier for Connection.BulkUpload(). + allContainersLimit = 10000 // Number of containers to fetch at once + allObjectsLimit = 10000 // Number objects to fetch at once + allObjectsChanLimit = 1000 // ...when fetching to a channel +) + +// Connection holds the details of the connection to the swift server. +// +// You need to provide UserName, ApiKey and AuthUrl when you create a +// connection then call Authenticate on it. +// +// The auth version in use will be detected from the AuthURL - you can +// override this with the AuthVersion parameter. +// +// If using v2 auth you can also set Region in the Connection +// structure. If you don't set Region you will get the default region +// which may not be what you want. +// +// For reference some common AuthUrls looks like this: +// +// Rackspace US https://auth.api.rackspacecloud.com/v1.0 +// Rackspace UK https://lon.auth.api.rackspacecloud.com/v1.0 +// Rackspace v2 https://identity.api.rackspacecloud.com/v2.0 +// Memset Memstore UK https://auth.storage.memset.com/v1.0 +// Memstore v2 https://auth.storage.memset.com/v2.0 +// +// When using Google Appengine you must provide the Connection with an +// appengine-specific Transport: +// +// import ( +// "appengine/urlfetch" +// "fmt" +// "github.com/ncw/swift" +// ) +// +// func handler(w http.ResponseWriter, r *http.Request) { +// ctx := appengine.NewContext(r) +// tr := urlfetch.Transport{Context: ctx} +// c := swift.Connection{ +// UserName: "user", +// ApiKey: "key", +// AuthUrl: "auth_url", +// Transport: tr, +// } +// _ := c.Authenticate() +// containers, _ := c.ContainerNames(nil) +// fmt.Fprintf(w, "containers: %q", containers) +// } +// +// If you don't supply a Transport, one is made which relies on +// http.ProxyFromEnvironment (http://golang.org/pkg/net/http/#ProxyFromEnvironment). +// This means that the connection will respect the HTTP proxy specified by the +// environment variables $HTTP_PROXY and $NO_PROXY. +type Connection struct { + // Parameters - fill these in before calling Authenticate + // They are all optional except UserName, ApiKey and AuthUrl + UserName string // UserName for api + ApiKey string // Key for api access + AuthUrl string // Auth URL + Retries int // Retries on error (default is 3) + UserAgent string // Http User agent (default goswift/1.0) + ConnectTimeout time.Duration // Connect channel timeout (default 10s) + Timeout time.Duration // Data channel timeout (default 60s) + Region string // Region to use eg "LON", "ORD" - default is use first region (V2 auth only) + AuthVersion int // Set to 1 or 2 or leave at 0 for autodetect + Internal bool // Set this to true to use the the internal / service network + Tenant string // Name of the tenant (v2 auth only) + TenantId string // Id of the tenant (v2 auth only) + Transport http.RoundTripper `json:"-" xml:"-"` // Optional specialised http.Transport (eg. for Google Appengine) + // These are filled in after Authenticate is called as are the defaults for above + StorageUrl string + AuthToken string + client *http.Client + Auth Authenticator `json:"-" xml:"-"` // the current authenticator + authLock sync.Mutex // lock when R/W StorageUrl, AuthToken, Auth +} + +// Error - all errors generated by this package are of this type. Other error +// may be passed on from library functions though. +type Error struct { + StatusCode int // HTTP status code if relevant or 0 if not + Text string +} + +// Error satisfy the error interface. +func (e *Error) Error() string { + return e.Text +} + +// newError make a new error from a string. +func newError(StatusCode int, Text string) *Error { + return &Error{ + StatusCode: StatusCode, + Text: Text, + } +} + +// newErrorf makes a new error from sprintf parameters. +func newErrorf(StatusCode int, Text string, Parameters ...interface{}) *Error { + return newError(StatusCode, fmt.Sprintf(Text, Parameters...)) +} + +// errorMap defines http error codes to error mappings. +type errorMap map[int]error + +var ( + // Specific Errors you might want to check for equality + BadRequest = newError(400, "Bad Request") + AuthorizationFailed = newError(401, "Authorization Failed") + ContainerNotFound = newError(404, "Container Not Found") + ContainerNotEmpty = newError(409, "Container Not Empty") + ObjectNotFound = newError(404, "Object Not Found") + ObjectCorrupted = newError(422, "Object Corrupted") + TimeoutError = newError(408, "Timeout when reading or writing data") + Forbidden = newError(403, "Operation forbidden") + TooLargeObject = newError(413, "Too Large Object") + + // Mappings for authentication errors + authErrorMap = errorMap{ + 400: BadRequest, + 401: AuthorizationFailed, + 403: Forbidden, + } + + // Mappings for container errors + ContainerErrorMap = errorMap{ + 400: BadRequest, + 403: Forbidden, + 404: ContainerNotFound, + 409: ContainerNotEmpty, + } + + // Mappings for object errors + objectErrorMap = errorMap{ + 400: BadRequest, + 403: Forbidden, + 404: ObjectNotFound, + 413: TooLargeObject, + 422: ObjectCorrupted, + } +) + +// checkClose is used to check the return from Close in a defer +// statement. +func checkClose(c io.Closer, err *error) { + cerr := c.Close() + if *err == nil { + *err = cerr + } +} + +// parseHeaders checks a response for errors and translates into +// standard errors if necessary. +func (c *Connection) parseHeaders(resp *http.Response, errorMap errorMap) error { + if errorMap != nil { + if err, ok := errorMap[resp.StatusCode]; ok { + return err + } + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return newErrorf(resp.StatusCode, "HTTP Error: %d: %s", resp.StatusCode, resp.Status) + } + return nil +} + +// readHeaders returns a Headers object from the http.Response. +// +// Logs a warning if receives multiple values for a key (which +// should never happen) +func readHeaders(resp *http.Response) Headers { + headers := Headers{} + for key, values := range resp.Header { + headers[key] = values[0] + if len(values) > 1 { + log.Printf("swift: received multiple values for header %q", key) + } + } + return headers +} + +// Headers stores HTTP headers (can only have one of each header like Swift). +type Headers map[string]string + +// Does an http request using the running timer passed in +func (c *Connection) doTimeoutRequest(timer *time.Timer, req *http.Request) (*http.Response, error) { + // Do the request in the background so we can check the timeout + type result struct { + resp *http.Response + err error + } + done := make(chan result, 1) + go func() { + resp, err := c.client.Do(req) + done <- result{resp, err} + }() + // Wait for the read or the timeout + select { + case r := <-done: + return r.resp, r.err + case <-timer.C: + // Kill the connection on timeout so we don't leak sockets or goroutines + cancelRequest(c.Transport, req) + return nil, TimeoutError + } + panic("unreachable") // For Go 1.0 +} + +// Set defaults for any unset values +// +// Call with authLock held +func (c *Connection) setDefaults() { + if c.UserAgent == "" { + c.UserAgent = DefaultUserAgent + } + if c.Retries == 0 { + c.Retries = DefaultRetries + } + if c.ConnectTimeout == 0 { + c.ConnectTimeout = 10 * time.Second + } + if c.Timeout == 0 { + c.Timeout = 60 * time.Second + } + if c.Transport == nil { + c.Transport = &http.Transport{ + // TLSClientConfig: &tls.Config{RootCAs: pool}, + // DisableCompression: true, + Proxy: http.ProxyFromEnvironment, + MaxIdleConnsPerHost: 2048, + } + } + if c.client == nil { + c.client = &http.Client{ + // CheckRedirect: redirectPolicyFunc, + Transport: c.Transport, + } + } +} + +// Authenticate connects to the Swift server. +// +// If you don't call it before calling one of the connection methods +// then it will be called for you on the first access. +func (c *Connection) Authenticate() (err error) { + c.authLock.Lock() + defer c.authLock.Unlock() + return c.authenticate() +} + +// Internal implementation of Authenticate +// +// Call with authLock held +func (c *Connection) authenticate() (err error) { + c.setDefaults() + + // Flush the keepalives connection - if we are + // re-authenticating then stuff has gone wrong + flushKeepaliveConnections(c.Transport) + + if c.Auth == nil { + c.Auth, err = newAuth(c) + if err != nil { + return + } + } + + retries := 1 +again: + var req *http.Request + req, err = c.Auth.Request(c) + if err != nil { + return + } + timer := time.NewTimer(c.ConnectTimeout) + var resp *http.Response + resp, err = c.doTimeoutRequest(timer, req) + if err != nil { + return + } + defer func() { + checkClose(resp.Body, &err) + // Flush the auth connection - we don't want to keep + // it open if keepalives were enabled + flushKeepaliveConnections(c.Transport) + }() + if err = c.parseHeaders(resp, authErrorMap); err != nil { + // Try again for a limited number of times on + // AuthorizationFailed or BadRequest. This allows us + // to try some alternate forms of the request + if (err == AuthorizationFailed || err == BadRequest) && retries > 0 { + retries-- + goto again + } + return + } + err = c.Auth.Response(resp) + if err != nil { + return + } + c.StorageUrl = c.Auth.StorageUrl(c.Internal) + c.AuthToken = c.Auth.Token() + if !c.authenticated() { + err = newError(0, "Response didn't have storage url and auth token") + return + } + return +} + +// Get an authToken and url +// +// The Url may be updated if it needed to authenticate using the OnReAuth function +func (c *Connection) getUrlAndAuthToken(targetUrlIn string, OnReAuth func() (string, error)) (targetUrlOut, authToken string, err error) { + c.authLock.Lock() + defer c.authLock.Unlock() + targetUrlOut = targetUrlIn + if !c.authenticated() { + err = c.authenticate() + if err != nil { + return + } + if OnReAuth != nil { + targetUrlOut, err = OnReAuth() + if err != nil { + return + } + } + } + authToken = c.AuthToken + return +} + +// flushKeepaliveConnections is called to flush pending requests after an error. +func flushKeepaliveConnections(transport http.RoundTripper) { + if tr, ok := transport.(interface { + CloseIdleConnections() + }); ok { + tr.CloseIdleConnections() + } +} + +// UnAuthenticate removes the authentication from the Connection. +func (c *Connection) UnAuthenticate() { + c.authLock.Lock() + c.StorageUrl = "" + c.AuthToken = "" + c.authLock.Unlock() +} + +// Authenticated returns a boolean to show if the current connection +// is authenticated. +// +// Doesn't actually check the credentials against the server. +func (c *Connection) Authenticated() bool { + c.authLock.Lock() + defer c.authLock.Unlock() + return c.authenticated() +} + +// Internal version of Authenticated() +// +// Call with authLock held +func (c *Connection) authenticated() bool { + return c.StorageUrl != "" && c.AuthToken != "" +} + +// RequestOpts contains parameters for Connection.storage. +type RequestOpts struct { + Container string + ObjectName string + Operation string + Parameters url.Values + Headers Headers + ErrorMap errorMap + NoResponse bool + Body io.Reader + Retries int + // if set this is called on re-authentication to refresh the targetUrl + OnReAuth func() (string, error) +} + +// Call runs a remote command on the targetUrl, returns a +// response, headers and possible error. +// +// operation is GET, HEAD etc +// container is the name of a container +// Any other parameters (if not None) are added to the targetUrl +// +// Returns a response or an error. If response is returned then +// resp.Body.Close() must be called on it, unless noResponse is set in +// which case the body will be closed in this function +// +// This will Authenticate if necessary, and re-authenticate if it +// receives a 401 error which means the token has expired +// +// This method is exported so extensions can call it. +func (c *Connection) Call(targetUrl string, p RequestOpts) (resp *http.Response, headers Headers, err error) { + c.authLock.Lock() + c.setDefaults() + c.authLock.Unlock() + retries := p.Retries + if retries == 0 { + retries = c.Retries + } + var req *http.Request + for { + var authToken string + targetUrl, authToken, err = c.getUrlAndAuthToken(targetUrl, p.OnReAuth) + + var url *url.URL + url, err = url.Parse(targetUrl) + if err != nil { + return + } + if p.Container != "" { + url.Path += "/" + p.Container + if p.ObjectName != "" { + url.Path += "/" + p.ObjectName + } + } + if p.Parameters != nil { + url.RawQuery = p.Parameters.Encode() + } + timer := time.NewTimer(c.ConnectTimeout) + reader := p.Body + if reader != nil { + reader = newWatchdogReader(reader, c.Timeout, timer) + } + req, err = http.NewRequest(p.Operation, url.String(), reader) + if err != nil { + return + } + if p.Headers != nil { + for k, v := range p.Headers { + req.Header.Add(k, v) + } + } + req.Header.Add("User-Agent", DefaultUserAgent) + req.Header.Add("X-Auth-Token", authToken) + resp, err = c.doTimeoutRequest(timer, req) + if err != nil { + return + } + // Check to see if token has expired + if resp.StatusCode == 401 && retries > 0 { + _ = resp.Body.Close() + c.UnAuthenticate() + retries-- + } else { + break + } + } + + if err = c.parseHeaders(resp, p.ErrorMap); err != nil { + _ = resp.Body.Close() + return nil, nil, err + } + headers = readHeaders(resp) + if p.NoResponse { + err = resp.Body.Close() + if err != nil { + return nil, nil, err + } + } else { + // Cancel the request on timeout + cancel := func() { + cancelRequest(c.Transport, req) + } + // Wrap resp.Body to make it obey an idle timeout + resp.Body = newTimeoutReader(resp.Body, c.Timeout, cancel) + } + return +} + +// storage runs a remote command on a the storage url, returns a +// response, headers and possible error. +// +// operation is GET, HEAD etc +// container is the name of a container +// Any other parameters (if not None) are added to the storage url +// +// Returns a response or an error. If response is returned then +// resp.Body.Close() must be called on it, unless noResponse is set in +// which case the body will be closed in this function +// +// This will Authenticate if necessary, and re-authenticate if it +// receives a 401 error which means the token has expired +func (c *Connection) storage(p RequestOpts) (resp *http.Response, headers Headers, err error) { + p.OnReAuth = func() (string, error) { + return c.StorageUrl, nil + } + c.authLock.Lock() + url := c.StorageUrl + c.authLock.Unlock() + return c.Call(url, p) +} + +// readLines reads the response into an array of strings. +// +// Closes the response when done +func readLines(resp *http.Response) (lines []string, err error) { + defer checkClose(resp.Body, &err) + reader := bufio.NewReader(resp.Body) + buffer := bytes.NewBuffer(make([]byte, 0, 128)) + var part []byte + var prefix bool + for { + if part, prefix, err = reader.ReadLine(); err != nil { + break + } + buffer.Write(part) + if !prefix { + lines = append(lines, buffer.String()) + buffer.Reset() + } + } + if err == io.EOF { + err = nil + } + return +} + +// readJson reads the response into the json type passed in +// +// Closes the response when done +func readJson(resp *http.Response, result interface{}) (err error) { + defer checkClose(resp.Body, &err) + decoder := json.NewDecoder(resp.Body) + return decoder.Decode(result) +} + +/* ------------------------------------------------------------ */ + +// ContainersOpts is options for Containers() and ContainerNames() +type ContainersOpts struct { + Limit int // For an integer value n, limits the number of results to at most n values. + Marker string // Given a string value x, return object names greater in value than the specified marker. + EndMarker string // Given a string value x, return container names less in value than the specified marker. + Headers Headers // Any additional HTTP headers - can be nil +} + +// parse the ContainerOpts +func (opts *ContainersOpts) parse() (url.Values, Headers) { + v := url.Values{} + var h Headers + if opts != nil { + if opts.Limit > 0 { + v.Set("limit", strconv.Itoa(opts.Limit)) + } + if opts.Marker != "" { + v.Set("marker", opts.Marker) + } + if opts.EndMarker != "" { + v.Set("end_marker", opts.EndMarker) + } + h = opts.Headers + } + return v, h +} + +// ContainerNames returns a slice of names of containers in this account. +func (c *Connection) ContainerNames(opts *ContainersOpts) ([]string, error) { + v, h := opts.parse() + resp, _, err := c.storage(RequestOpts{ + Operation: "GET", + Parameters: v, + ErrorMap: ContainerErrorMap, + Headers: h, + }) + if err != nil { + return nil, err + } + lines, err := readLines(resp) + return lines, err +} + +// Container contains information about a container +type Container struct { + Name string // Name of the container + Count int64 // Number of objects in the container + Bytes int64 // Total number of bytes used in the container +} + +// Containers returns a slice of structures with full information as +// described in Container. +func (c *Connection) Containers(opts *ContainersOpts) ([]Container, error) { + v, h := opts.parse() + v.Set("format", "json") + resp, _, err := c.storage(RequestOpts{ + Operation: "GET", + Parameters: v, + ErrorMap: ContainerErrorMap, + Headers: h, + }) + if err != nil { + return nil, err + } + var containers []Container + err = readJson(resp, &containers) + return containers, err +} + +// containersAllOpts makes a copy of opts if set or makes a new one and +// overrides Limit and Marker +func containersAllOpts(opts *ContainersOpts) *ContainersOpts { + var newOpts ContainersOpts + if opts != nil { + newOpts = *opts + } + if newOpts.Limit == 0 { + newOpts.Limit = allContainersLimit + } + newOpts.Marker = "" + return &newOpts +} + +// ContainersAll is like Containers but it returns all the Containers +// +// It calls Containers multiple times using the Marker parameter +// +// It has a default Limit parameter but you may pass in your own +func (c *Connection) ContainersAll(opts *ContainersOpts) ([]Container, error) { + opts = containersAllOpts(opts) + containers := make([]Container, 0) + for { + newContainers, err := c.Containers(opts) + if err != nil { + return nil, err + } + containers = append(containers, newContainers...) + if len(newContainers) < opts.Limit { + break + } + opts.Marker = newContainers[len(newContainers)-1].Name + } + return containers, nil +} + +// ContainerNamesAll is like ContainerNamess but it returns all the Containers +// +// It calls ContainerNames multiple times using the Marker parameter +// +// It has a default Limit parameter but you may pass in your own +func (c *Connection) ContainerNamesAll(opts *ContainersOpts) ([]string, error) { + opts = containersAllOpts(opts) + containers := make([]string, 0) + for { + newContainers, err := c.ContainerNames(opts) + if err != nil { + return nil, err + } + containers = append(containers, newContainers...) + if len(newContainers) < opts.Limit { + break + } + opts.Marker = newContainers[len(newContainers)-1] + } + return containers, nil +} + +/* ------------------------------------------------------------ */ + +// ObjectOpts is options for Objects() and ObjectNames() +type ObjectsOpts struct { + Limit int // For an integer value n, limits the number of results to at most n values. + Marker string // Given a string value x, return object names greater in value than the specified marker. + EndMarker string // Given a string value x, return object names less in value than the specified marker + Prefix string // For a string value x, causes the results to be limited to object names beginning with the substring x. + Path string // For a string value x, return the object names nested in the pseudo path + Delimiter rune // For a character c, return all the object names nested in the container + Headers Headers // Any additional HTTP headers - can be nil +} + +// parse reads values out of ObjectsOpts +func (opts *ObjectsOpts) parse() (url.Values, Headers) { + v := url.Values{} + var h Headers + if opts != nil { + if opts.Limit > 0 { + v.Set("limit", strconv.Itoa(opts.Limit)) + } + if opts.Marker != "" { + v.Set("marker", opts.Marker) + } + if opts.EndMarker != "" { + v.Set("end_marker", opts.EndMarker) + } + if opts.Prefix != "" { + v.Set("prefix", opts.Prefix) + } + if opts.Path != "" { + v.Set("path", opts.Path) + } + if opts.Delimiter != 0 { + v.Set("delimiter", string(opts.Delimiter)) + } + h = opts.Headers + } + return v, h +} + +// ObjectNames returns a slice of names of objects in a given container. +func (c *Connection) ObjectNames(container string, opts *ObjectsOpts) ([]string, error) { + v, h := opts.parse() + resp, _, err := c.storage(RequestOpts{ + Container: container, + Operation: "GET", + Parameters: v, + ErrorMap: ContainerErrorMap, + Headers: h, + }) + if err != nil { + return nil, err + } + return readLines(resp) +} + +// Object contains information about an object +type Object struct { + Name string `json:"name"` // object name + ContentType string `json:"content_type"` // eg application/directory + Bytes int64 `json:"bytes"` // size in bytes + ServerLastModified string `json:"last_modified"` // Last modified time, eg '2011-06-30T08:20:47.736680' as a string supplied by the server + LastModified time.Time // Last modified time converted to a time.Time + Hash string `json:"hash"` // MD5 hash, eg "d41d8cd98f00b204e9800998ecf8427e" + PseudoDirectory bool // Set when using delimiter to show that this directory object does not really exist + SubDir string `json:"subdir"` // returned only when using delimiter to mark "pseudo directories" +} + +// Objects returns a slice of Object with information about each +// object in the container. +// +// If Delimiter is set in the opts then PseudoDirectory may be set, +// with ContentType 'application/directory'. These are not real +// objects but represent directories of objects which haven't had an +// object created for them. +func (c *Connection) Objects(container string, opts *ObjectsOpts) ([]Object, error) { + v, h := opts.parse() + v.Set("format", "json") + resp, _, err := c.storage(RequestOpts{ + Container: container, + Operation: "GET", + Parameters: v, + ErrorMap: ContainerErrorMap, + Headers: h, + }) + if err != nil { + return nil, err + } + var objects []Object + err = readJson(resp, &objects) + // Convert Pseudo directories and dates + for i := range objects { + object := &objects[i] + if object.SubDir != "" { + object.Name = object.SubDir + object.PseudoDirectory = true + object.ContentType = "application/directory" + } + if object.ServerLastModified != "" { + // 2012-11-11T14:49:47.887250 + // + // Remove fractional seconds if present. This + // then keeps it consistent with Object + // which can only return timestamps accurate + // to 1 second + // + // The TimeFormat will parse fractional + // seconds if desired though + datetime := strings.SplitN(object.ServerLastModified, ".", 2)[0] + object.LastModified, err = time.Parse(TimeFormat, datetime) + if err != nil { + return nil, err + } + } + } + return objects, err +} + +// objectsAllOpts makes a copy of opts if set or makes a new one and +// overrides Limit and Marker +func objectsAllOpts(opts *ObjectsOpts, Limit int) *ObjectsOpts { + var newOpts ObjectsOpts + if opts != nil { + newOpts = *opts + } + if newOpts.Limit == 0 { + newOpts.Limit = Limit + } + newOpts.Marker = "" + return &newOpts +} + +// A closure defined by the caller to iterate through all objects +// +// Call Objects or ObjectNames from here with the *ObjectOpts passed in +// +// Do whatever is required with the results then return them +type ObjectsWalkFn func(*ObjectsOpts) (interface{}, error) + +// ObjectsWalk is uses to iterate through all the objects in chunks as +// returned by Objects or ObjectNames using the Marker and Limit +// parameters in the ObjectsOpts. +// +// Pass in a closure `walkFn` which calls Objects or ObjectNames with +// the *ObjectsOpts passed to it and does something with the results. +// +// Errors will be returned from this function +// +// It has a default Limit parameter but you may pass in your own +func (c *Connection) ObjectsWalk(container string, opts *ObjectsOpts, walkFn ObjectsWalkFn) error { + opts = objectsAllOpts(opts, allObjectsChanLimit) + for { + objects, err := walkFn(opts) + if err != nil { + return err + } + var n int + var last string + switch objects := objects.(type) { + case []string: + n = len(objects) + if n > 0 { + last = objects[len(objects)-1] + } + case []Object: + n = len(objects) + if n > 0 { + last = objects[len(objects)-1].Name + } + default: + panic("Unknown type returned to ObjectsWalk") + } + if n < opts.Limit { + break + } + opts.Marker = last + } + return nil +} + +// ObjectsAll is like Objects but it returns an unlimited number of Objects in a slice +// +// It calls Objects multiple times using the Marker parameter +func (c *Connection) ObjectsAll(container string, opts *ObjectsOpts) ([]Object, error) { + objects := make([]Object, 0) + err := c.ObjectsWalk(container, opts, func(opts *ObjectsOpts) (interface{}, error) { + newObjects, err := c.Objects(container, opts) + if err == nil { + objects = append(objects, newObjects...) + } + return newObjects, err + }) + return objects, err +} + +// ObjectNamesAll is like ObjectNames but it returns all the Objects +// +// It calls ObjectNames multiple times using the Marker parameter +// +// It has a default Limit parameter but you may pass in your own +func (c *Connection) ObjectNamesAll(container string, opts *ObjectsOpts) ([]string, error) { + objects := make([]string, 0) + err := c.ObjectsWalk(container, opts, func(opts *ObjectsOpts) (interface{}, error) { + newObjects, err := c.ObjectNames(container, opts) + if err == nil { + objects = append(objects, newObjects...) + } + return newObjects, err + }) + return objects, err +} + +// Account contains information about this account. +type Account struct { + BytesUsed int64 // total number of bytes used + Containers int64 // total number of containers + Objects int64 // total number of objects +} + +// getInt64FromHeader is a helper function to decode int64 from header. +func getInt64FromHeader(resp *http.Response, header string) (result int64, err error) { + value := resp.Header.Get(header) + result, err = strconv.ParseInt(value, 10, 64) + if err != nil { + err = newErrorf(0, "Bad Header '%s': '%s': %s", header, value, err) + } + return +} + +// Account returns info about the account in an Account struct. +func (c *Connection) Account() (info Account, headers Headers, err error) { + var resp *http.Response + resp, headers, err = c.storage(RequestOpts{ + Operation: "HEAD", + ErrorMap: ContainerErrorMap, + NoResponse: true, + }) + if err != nil { + return + } + // Parse the headers into a dict + // + // {'Accept-Ranges': 'bytes', + // 'Content-Length': '0', + // 'Date': 'Tue, 05 Jul 2011 16:37:06 GMT', + // 'X-Account-Bytes-Used': '316598182', + // 'X-Account-Container-Count': '4', + // 'X-Account-Object-Count': '1433'} + if info.BytesUsed, err = getInt64FromHeader(resp, "X-Account-Bytes-Used"); err != nil { + return + } + if info.Containers, err = getInt64FromHeader(resp, "X-Account-Container-Count"); err != nil { + return + } + if info.Objects, err = getInt64FromHeader(resp, "X-Account-Object-Count"); err != nil { + return + } + return +} + +// AccountUpdate adds, replaces or remove account metadata. +// +// Add or update keys by mentioning them in the Headers. +// +// Remove keys by setting them to an empty string. +func (c *Connection) AccountUpdate(h Headers) error { + _, _, err := c.storage(RequestOpts{ + Operation: "POST", + ErrorMap: ContainerErrorMap, + NoResponse: true, + Headers: h, + }) + return err +} + +// ContainerCreate creates a container. +// +// If you don't want to add Headers just pass in nil +// +// No error is returned if it already exists but the metadata if any will be updated. +func (c *Connection) ContainerCreate(container string, h Headers) error { + _, _, err := c.storage(RequestOpts{ + Container: container, + Operation: "PUT", + ErrorMap: ContainerErrorMap, + NoResponse: true, + Headers: h, + }) + return err +} + +// ContainerDelete deletes a container. +// +// May return ContainerDoesNotExist or ContainerNotEmpty +func (c *Connection) ContainerDelete(container string) error { + _, _, err := c.storage(RequestOpts{ + Container: container, + Operation: "DELETE", + ErrorMap: ContainerErrorMap, + NoResponse: true, + }) + return err +} + +// Container returns info about a single container including any +// metadata in the headers. +func (c *Connection) Container(container string) (info Container, headers Headers, err error) { + var resp *http.Response + resp, headers, err = c.storage(RequestOpts{ + Container: container, + Operation: "HEAD", + ErrorMap: ContainerErrorMap, + NoResponse: true, + }) + if err != nil { + return + } + // Parse the headers into the struct + info.Name = container + if info.Bytes, err = getInt64FromHeader(resp, "X-Container-Bytes-Used"); err != nil { + return + } + if info.Count, err = getInt64FromHeader(resp, "X-Container-Object-Count"); err != nil { + return + } + return +} + +// ContainerUpdate adds, replaces or removes container metadata. +// +// Add or update keys by mentioning them in the Metadata. +// +// Remove keys by setting them to an empty string. +// +// Container metadata can only be read with Container() not with Containers(). +func (c *Connection) ContainerUpdate(container string, h Headers) error { + _, _, err := c.storage(RequestOpts{ + Container: container, + Operation: "POST", + ErrorMap: ContainerErrorMap, + NoResponse: true, + Headers: h, + }) + return err +} + +// ------------------------------------------------------------ + +// ObjectCreateFile represents a swift object open for writing +type ObjectCreateFile struct { + checkHash bool // whether we are checking the hash + pipeReader *io.PipeReader // pipe for the caller to use + pipeWriter *io.PipeWriter + hash hash.Hash // hash being build up as we go along + done chan struct{} // signals when the upload has finished + resp *http.Response // valid when done has signalled + err error // ditto + headers Headers // ditto +} + +// Write bytes to the object - see io.Writer +func (file *ObjectCreateFile) Write(p []byte) (n int, err error) { + // Check to see if write has finished already + select { + case <-file.done: + if file.err != nil { + return 0, file.err + } + return 0, newError(500, "Write on closed file") + default: + } + if file.checkHash { + _, _ = file.hash.Write(p) + } + return file.pipeWriter.Write(p) +} + +// Close the object and checks the md5sum if it was required. +// +// Also returns any other errors from the server (eg container not +// found) so it is very important to check the errors on this method. +func (file *ObjectCreateFile) Close() error { + // Close the body + err := file.pipeWriter.Close() + if err != nil { + return err + } + + // Wait for the HTTP operation to complete + <-file.done + + // Check errors + if file.err != nil { + return file.err + } + if file.checkHash { + receivedMd5 := strings.ToLower(file.headers["Etag"]) + calculatedMd5 := fmt.Sprintf("%x", file.hash.Sum(nil)) + if receivedMd5 != calculatedMd5 { + return ObjectCorrupted + } + } + return nil +} + +// Check it satisfies the interface +var _ io.WriteCloser = &ObjectCreateFile{} + +// objectPutHeaders create a set of headers for a PUT +// +// It guesses the contentType from the objectName if it isn't set +// +// checkHash may be changed +func objectPutHeaders(objectName string, checkHash *bool, Hash string, contentType string, h Headers) Headers { + if contentType == "" { + contentType = mime.TypeByExtension(path.Ext(objectName)) + if contentType == "" { + contentType = "application/octet-stream" + } + } + // Meta stuff + extraHeaders := map[string]string{ + "Content-Type": contentType, + } + for key, value := range h { + extraHeaders[key] = value + } + if Hash != "" { + extraHeaders["Etag"] = Hash + *checkHash = false // the server will do it + } + return extraHeaders +} + +// ObjectCreate creates or updates the object in the container. It +// returns an io.WriteCloser you should write the contents to. You +// MUST call Close() on it and you MUST check the error return from +// Close(). +// +// If checkHash is True then it will calculate the MD5 Hash of the +// file as it is being uploaded and check it against that returned +// from the server. If it is wrong then it will return +// ObjectCorrupted on Close() +// +// If you know the MD5 hash of the object ahead of time then set the +// Hash parameter and it will be sent to the server (as an Etag +// header) and the server will check the MD5 itself after the upload, +// and this will return ObjectCorrupted on Close() if it is incorrect. +// +// If you don't want any error protection (not recommended) then set +// checkHash to false and Hash to "". +// +// If contentType is set it will be used, otherwise one will be +// guessed from objectName using mime.TypeByExtension +func (c *Connection) ObjectCreate(container string, objectName string, checkHash bool, Hash string, contentType string, h Headers) (file *ObjectCreateFile, err error) { + extraHeaders := objectPutHeaders(objectName, &checkHash, Hash, contentType, h) + pipeReader, pipeWriter := io.Pipe() + file = &ObjectCreateFile{ + hash: md5.New(), + checkHash: checkHash, + pipeReader: pipeReader, + pipeWriter: pipeWriter, + done: make(chan struct{}), + } + // Run the PUT in the background piping it data + go func() { + file.resp, file.headers, file.err = c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "PUT", + Headers: extraHeaders, + Body: pipeReader, + NoResponse: true, + ErrorMap: objectErrorMap, + }) + // Signal finished + close(file.done) + }() + return +} + +// ObjectPut creates or updates the path in the container from +// contents. contents should be an open io.Reader which will have all +// its contents read. +// +// This is a low level interface. +// +// If checkHash is True then it will calculate the MD5 Hash of the +// file as it is being uploaded and check it against that returned +// from the server. If it is wrong then it will return +// ObjectCorrupted. +// +// If you know the MD5 hash of the object ahead of time then set the +// Hash parameter and it will be sent to the server (as an Etag +// header) and the server will check the MD5 itself after the upload, +// and this will return ObjectCorrupted if it is incorrect. +// +// If you don't want any error protection (not recommended) then set +// checkHash to false and Hash to "". +// +// If contentType is set it will be used, otherwise one will be +// guessed from objectName using mime.TypeByExtension +func (c *Connection) ObjectPut(container string, objectName string, contents io.Reader, checkHash bool, Hash string, contentType string, h Headers) (headers Headers, err error) { + extraHeaders := objectPutHeaders(objectName, &checkHash, Hash, contentType, h) + hash := md5.New() + var body io.Reader = contents + if checkHash { + body = io.TeeReader(contents, hash) + } + _, headers, err = c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "PUT", + Headers: extraHeaders, + Body: body, + NoResponse: true, + ErrorMap: objectErrorMap, + }) + if err != nil { + return + } + if checkHash { + receivedMd5 := strings.ToLower(headers["Etag"]) + calculatedMd5 := fmt.Sprintf("%x", hash.Sum(nil)) + if receivedMd5 != calculatedMd5 { + err = ObjectCorrupted + return + } + } + return +} + +// ObjectPutBytes creates an object from a []byte in a container. +// +// This is a simplified interface which checks the MD5. +func (c *Connection) ObjectPutBytes(container string, objectName string, contents []byte, contentType string) (err error) { + buf := bytes.NewBuffer(contents) + _, err = c.ObjectPut(container, objectName, buf, true, "", contentType, nil) + return +} + +// ObjectPutString creates an object from a string in a container. +// +// This is a simplified interface which checks the MD5 +func (c *Connection) ObjectPutString(container string, objectName string, contents string, contentType string) (err error) { + buf := strings.NewReader(contents) + _, err = c.ObjectPut(container, objectName, buf, true, "", contentType, nil) + return +} + +// ObjectOpenFile represents a swift object open for reading +type ObjectOpenFile struct { + connection *Connection // stored copy of Connection used in Open + container string // stored copy of container used in Open + objectName string // stored copy of objectName used in Open + headers Headers // stored copy of headers used in Open + resp *http.Response // http connection + body io.Reader // read data from this + checkHash bool // true if checking MD5 + hash hash.Hash // currently accumulating MD5 + bytes int64 // number of bytes read on this connection + eof bool // whether we have read end of file + pos int64 // current position when reading + lengthOk bool // whether length is valid + length int64 // length of the object if read + seeked bool // whether we have seeked this file or not +} + +// Read bytes from the object - see io.Reader +func (file *ObjectOpenFile) Read(p []byte) (n int, err error) { + n, err = file.body.Read(p) + file.bytes += int64(n) + file.pos += int64(n) + if err == io.EOF { + file.eof = true + } + return +} + +// Seek sets the offset for the next Read to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 +// means relative to the current offset, and 2 means relative to the +// end. Seek returns the new offset and an Error, if any. +// +// Seek uses HTTP Range headers which, if the file pointer is moved, +// will involve reopening the HTTP connection. +// +// Note that you can't seek to the end of a file or beyond; HTTP Range +// requests don't support the file pointer being outside the data, +// unlike os.File +// +// Seek(0, 1) will return the current file pointer. +func (file *ObjectOpenFile) Seek(offset int64, whence int) (newPos int64, err error) { + switch whence { + case 0: // relative to start + newPos = offset + case 1: // relative to current + newPos = file.pos + offset + case 2: // relative to end + if !file.lengthOk { + return file.pos, newError(0, "Length of file unknown so can't seek from end") + } + newPos = file.length + offset + default: + panic("Unknown whence in ObjectOpenFile.Seek") + } + // If at correct position (quite likely), do nothing + if newPos == file.pos { + return + } + // Close the file... + file.seeked = true + err = file.Close() + if err != nil { + return + } + // ...and re-open with a Range header + if file.headers == nil { + file.headers = Headers{} + } + if newPos > 0 { + file.headers["Range"] = fmt.Sprintf("bytes=%d-", newPos) + } else { + delete(file.headers, "Range") + } + newFile, _, err := file.connection.ObjectOpen(file.container, file.objectName, false, file.headers) + if err != nil { + return + } + // Update the file + file.resp = newFile.resp + file.body = newFile.body + file.checkHash = false + file.pos = newPos + return +} + +// Length gets the objects content length either from a cached copy or +// from the server. +func (file *ObjectOpenFile) Length() (int64, error) { + if !file.lengthOk { + info, _, err := file.connection.Object(file.container, file.objectName) + file.length = info.Bytes + file.lengthOk = (err == nil) + return file.length, err + } + return file.length, nil +} + +// Close the object and checks the length and md5sum if it was +// required and all the object was read +func (file *ObjectOpenFile) Close() (err error) { + // Close the body at the end + defer checkClose(file.resp.Body, &err) + + // If not end of file or seeked then can't check anything + if !file.eof || file.seeked { + return + } + + // Check the MD5 sum if requested + if file.checkHash { + receivedMd5 := strings.ToLower(file.resp.Header.Get("Etag")) + calculatedMd5 := fmt.Sprintf("%x", file.hash.Sum(nil)) + if receivedMd5 != calculatedMd5 { + err = ObjectCorrupted + return + } + } + + // Check to see we read the correct number of bytes + if file.lengthOk && file.length != file.bytes { + err = ObjectCorrupted + return + } + return +} + +// Check it satisfies the interfaces +var _ io.ReadCloser = &ObjectOpenFile{} +var _ io.Seeker = &ObjectOpenFile{} + +// ObjectOpen returns an ObjectOpenFile for reading the contents of +// the object. This satisfies the io.ReadCloser and the io.Seeker +// interfaces. +// +// You must call Close() on contents when finished +// +// Returns the headers of the response. +// +// If checkHash is true then it will calculate the md5sum of the file +// as it is being received and check it against that returned from the +// server. If it is wrong then it will return ObjectCorrupted. It +// will also check the length returned. No checking will be done if +// you don't read all the contents. +// +// headers["Content-Type"] will give the content type if desired. +func (c *Connection) ObjectOpen(container string, objectName string, checkHash bool, h Headers) (file *ObjectOpenFile, headers Headers, err error) { + var resp *http.Response + resp, headers, err = c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "GET", + ErrorMap: objectErrorMap, + Headers: h, + }) + if err != nil { + return + } + file = &ObjectOpenFile{ + connection: c, + container: container, + objectName: objectName, + headers: h, + resp: resp, + checkHash: checkHash, + body: resp.Body, + } + if checkHash { + file.hash = md5.New() + file.body = io.TeeReader(resp.Body, file.hash) + } + // Read Content-Length + file.length, err = getInt64FromHeader(resp, "Content-Length") + file.lengthOk = (err == nil) + return +} + +// ObjectGet gets the object into the io.Writer contents. +// +// Returns the headers of the response. +// +// If checkHash is true then it will calculate the md5sum of the file +// as it is being received and check it against that returned from the +// server. If it is wrong then it will return ObjectCorrupted. +// +// headers["Content-Type"] will give the content type if desired. +func (c *Connection) ObjectGet(container string, objectName string, contents io.Writer, checkHash bool, h Headers) (headers Headers, err error) { + file, headers, err := c.ObjectOpen(container, objectName, checkHash, h) + if err != nil { + return + } + defer checkClose(file, &err) + _, err = io.Copy(contents, file) + return +} + +// ObjectGetBytes returns an object as a []byte. +// +// This is a simplified interface which checks the MD5 +func (c *Connection) ObjectGetBytes(container string, objectName string) (contents []byte, err error) { + var buf bytes.Buffer + _, err = c.ObjectGet(container, objectName, &buf, true, nil) + contents = buf.Bytes() + return +} + +// ObjectGetString returns an object as a string. +// +// This is a simplified interface which checks the MD5 +func (c *Connection) ObjectGetString(container string, objectName string) (contents string, err error) { + var buf bytes.Buffer + _, err = c.ObjectGet(container, objectName, &buf, true, nil) + contents = buf.String() + return +} + +// ObjectDelete deletes the object. +// +// May return ObjectNotFound if the object isn't found +func (c *Connection) ObjectDelete(container string, objectName string) error { + _, _, err := c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "DELETE", + ErrorMap: objectErrorMap, + }) + return err +} + +// parseResponseStatus parses string like "200 OK" and returns Error. +// +// For status codes beween 200 and 299, this returns nil. +func parseResponseStatus(resp string, errorMap errorMap) error { + code := 0 + reason := resp + t := strings.SplitN(resp, " ", 2) + if len(t) == 2 { + ncode, err := strconv.Atoi(t[0]) + if err == nil { + code = ncode + reason = t[1] + } + } + if errorMap != nil { + if err, ok := errorMap[code]; ok { + return err + } + } + if 200 <= code && code <= 299 { + return nil + } + return newError(code, reason) +} + +// BulkDeleteResult stores results of BulkDelete(). +// +// Individual errors may (or may not) be returned by Errors. +// Errors is a map whose keys are a full path of where the object was +// to be deleted, and whose values are Error objects. A full path of +// object looks like "/API_VERSION/USER_ACCOUNT/CONTAINER/OBJECT_PATH". +type BulkDeleteResult struct { + NumberNotFound int64 // # of objects not found. + NumberDeleted int64 // # of deleted objects. + Errors map[string]error // Mapping between object name and an error. + Headers Headers // Response HTTP headers. +} + +// BulkDelete deletes multiple objectNames from container in one operation. +// +// Some servers may not accept bulk-delete requests since bulk-delete is +// an optional feature of swift - these will return the Forbidden error. +// +// See also: +// * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html +// * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html +func (c *Connection) BulkDelete(container string, objectNames []string) (result BulkDeleteResult, err error) { + var buffer bytes.Buffer + for _, s := range objectNames { + buffer.WriteString(fmt.Sprintf("/%s/%s\n", container, + url.QueryEscape(s))) + } + resp, headers, err := c.storage(RequestOpts{ + Operation: "DELETE", + Parameters: url.Values{"bulk-delete": []string{"1"}}, + Headers: Headers{ + "Accept": "application/json", + "Content-Type": "text/plain", + }, + ErrorMap: ContainerErrorMap, + Body: &buffer, + }) + if err != nil { + return + } + var jsonResult struct { + NotFound int64 `json:"Number Not Found"` + Status string `json:"Response Status"` + Errors [][]string + Deleted int64 `json:"Number Deleted"` + } + err = readJson(resp, &jsonResult) + if err != nil { + return + } + + err = parseResponseStatus(jsonResult.Status, objectErrorMap) + result.NumberNotFound = jsonResult.NotFound + result.NumberDeleted = jsonResult.Deleted + result.Headers = headers + el := make(map[string]error, len(jsonResult.Errors)) + for _, t := range jsonResult.Errors { + if len(t) != 2 { + continue + } + el[t[0]] = parseResponseStatus(t[1], objectErrorMap) + } + result.Errors = el + return +} + +// BulkUploadResult stores results of BulkUpload(). +// +// Individual errors may (or may not) be returned by Errors. +// Errors is a map whose keys are a full path of where an object was +// to be created, and whose values are Error objects. A full path of +// object looks like "/API_VERSION/USER_ACCOUNT/CONTAINER/OBJECT_PATH". +type BulkUploadResult struct { + NumberCreated int64 // # of created objects. + Errors map[string]error // Mapping between object name and an error. + Headers Headers // Response HTTP headers. +} + +// BulkUpload uploads multiple files in one operation. +// +// uploadPath can be empty, a container name, or a pseudo-directory +// within a container. If uploadPath is empty, new containers may be +// automatically created. +// +// Files are read from dataStream. The format of the stream is specified +// by the format parameter. Available formats are: +// * UploadTar - Plain tar stream. +// * UploadTarGzip - Gzip compressed tar stream. +// * UploadTarBzip2 - Bzip2 compressed tar stream. +// +// Some servers may not accept bulk-upload requests since bulk-upload is +// an optional feature of swift - these will return the Forbidden error. +// +// See also: +// * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-extract-archive.html +// * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Extract_Archive-d1e2338.html +func (c *Connection) BulkUpload(uploadPath string, dataStream io.Reader, format string, h Headers) (result BulkUploadResult, err error) { + extraHeaders := Headers{"Accept": "application/json"} + for key, value := range h { + extraHeaders[key] = value + } + // The following code abuses Container parameter intentionally. + // The best fix might be to rename Container to UploadPath. + resp, headers, err := c.storage(RequestOpts{ + Container: uploadPath, + Operation: "PUT", + Parameters: url.Values{"extract-archive": []string{format}}, + Headers: extraHeaders, + ErrorMap: ContainerErrorMap, + Body: dataStream, + }) + if err != nil { + return + } + // Detect old servers which don't support this feature + if headers["Content-Type"] != "application/json" { + err = Forbidden + return + } + var jsonResult struct { + Created int64 `json:"Number Files Created"` + Status string `json:"Response Status"` + Errors [][]string + } + err = readJson(resp, &jsonResult) + if err != nil { + return + } + + err = parseResponseStatus(jsonResult.Status, objectErrorMap) + result.NumberCreated = jsonResult.Created + result.Headers = headers + el := make(map[string]error, len(jsonResult.Errors)) + for _, t := range jsonResult.Errors { + if len(t) != 2 { + continue + } + el[t[0]] = parseResponseStatus(t[1], objectErrorMap) + } + result.Errors = el + return +} + +// Object returns info about a single object including any metadata in the header. +// +// May return ObjectNotFound. +// +// Use headers.ObjectMetadata() to read the metadata in the Headers. +func (c *Connection) Object(container string, objectName string) (info Object, headers Headers, err error) { + var resp *http.Response + resp, headers, err = c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "HEAD", + ErrorMap: objectErrorMap, + NoResponse: true, + }) + if err != nil { + return + } + // Parse the headers into the struct + // HTTP/1.1 200 OK + // Date: Thu, 07 Jun 2010 20:59:39 GMT + // Server: Apache + // Last-Modified: Fri, 12 Jun 2010 13:40:18 GMT + // ETag: 8a964ee2a5e88be344f36c22562a6486 + // Content-Length: 512000 + // Content-Type: text/plain; charset=UTF-8 + // X-Object-Meta-Meat: Bacon + // X-Object-Meta-Fruit: Bacon + // X-Object-Meta-Veggie: Bacon + // X-Object-Meta-Dairy: Bacon + info.Name = objectName + info.ContentType = resp.Header.Get("Content-Type") + if info.Bytes, err = getInt64FromHeader(resp, "Content-Length"); err != nil { + return + } + info.ServerLastModified = resp.Header.Get("Last-Modified") + if info.LastModified, err = time.Parse(http.TimeFormat, info.ServerLastModified); err != nil { + return + } + info.Hash = resp.Header.Get("Etag") + return +} + +// ObjectUpdate adds, replaces or removes object metadata. +// +// Add or Update keys by mentioning them in the Metadata. Use +// Metadata.ObjectHeaders and Headers.ObjectMetadata to convert your +// Metadata to and from normal HTTP headers. +// +// This removes all metadata previously added to the object and +// replaces it with that passed in so to delete keys, just don't +// mention them the headers you pass in. +// +// Object metadata can only be read with Object() not with Objects(). +// +// This can also be used to set headers not already assigned such as +// X-Delete-At or X-Delete-After for expiring objects. +// +// You cannot use this to change any of the object's other headers +// such as Content-Type, ETag, etc. +// +// Refer to copying an object when you need to update metadata or +// other headers such as Content-Type or CORS headers. +// +// May return ObjectNotFound. +func (c *Connection) ObjectUpdate(container string, objectName string, h Headers) error { + _, _, err := c.storage(RequestOpts{ + Container: container, + ObjectName: objectName, + Operation: "POST", + ErrorMap: objectErrorMap, + NoResponse: true, + Headers: h, + }) + return err +} + +// ObjectCopy does a server side copy of an object to a new position +// +// All metadata is preserved. If metadata is set in the headers then +// it overrides the old metadata on the copied object. +// +// The destination container must exist before the copy. +// +// You can use this to copy an object to itself - this is the only way +// to update the content type of an object. +func (c *Connection) ObjectCopy(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string, h Headers) (headers Headers, err error) { + // Meta stuff + extraHeaders := map[string]string{ + "Destination": dstContainer + "/" + dstObjectName, + } + for key, value := range h { + extraHeaders[key] = value + } + _, headers, err = c.storage(RequestOpts{ + Container: srcContainer, + ObjectName: srcObjectName, + Operation: "COPY", + ErrorMap: objectErrorMap, + NoResponse: true, + Headers: extraHeaders, + }) + return +} + +// ObjectMove does a server side move of an object to a new position +// +// This is a convenience method which calls ObjectCopy then ObjectDelete +// +// All metadata is preserved. +// +// The destination container must exist before the copy. +func (c *Connection) ObjectMove(srcContainer string, srcObjectName string, dstContainer string, dstObjectName string) (err error) { + _, err = c.ObjectCopy(srcContainer, srcObjectName, dstContainer, dstObjectName, nil) + if err != nil { + return + } + return c.ObjectDelete(srcContainer, srcObjectName) +} + +// ObjectUpdateContentType updates the content type of an object +// +// This is a convenience method which calls ObjectCopy +// +// All other metadata is preserved. +func (c *Connection) ObjectUpdateContentType(container string, objectName string, contentType string) (err error) { + h := Headers{"Content-Type": contentType} + _, err = c.ObjectCopy(container, objectName, container, objectName, h) + return +} + +// ------------------------------------------------------------ + +// VersionContainerCreate is a helper method for creating and enabling version controlled containers. +// +// It builds the current object container, the non-current object version container, and enables versioning. +// +// If the server doesn't support versioning then it will return +// Forbidden however it will have created both the containers at that point. +func (c *Connection) VersionContainerCreate(current, version string) error { + if err := c.ContainerCreate(version, nil); err != nil { + return err + } + if err := c.ContainerCreate(current, nil); err != nil { + return err + } + if err := c.VersionEnable(current, version); err != nil { + return err + } + return nil +} + +// VersionEnable enables versioning on the current container with version as the tracking container. +// +// May return Forbidden if this isn't supported by the server +func (c *Connection) VersionEnable(current, version string) error { + h := Headers{"X-Versions-Location": version} + if err := c.ContainerUpdate(current, h); err != nil { + return err + } + // Check to see if the header was set properly + _, headers, err := c.Container(current) + if err != nil { + return err + } + // If failed to set versions header, return Forbidden as the server doesn't support this + if headers["X-Versions-Location"] != version { + return Forbidden + } + return nil +} + +// VersionDisable disables versioning on the current container. +func (c *Connection) VersionDisable(current string) error { + h := Headers{"X-Versions-Location": ""} + if err := c.ContainerUpdate(current, h); err != nil { + return err + } + return nil +} + +// VersionObjectList returns a list of older versions of the object. +// +// Objects are returned in the format / +func (c *Connection) VersionObjectList(version, object string) ([]string, error) { + opts := &ObjectsOpts{ + // <3-character zero-padded hexadecimal character length>/ + Prefix: fmt.Sprintf("%03x", len(object)) + object + "/", + } + return c.ObjectNames(version, opts) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_internal_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_internal_test.go new file mode 100644 index 00000000..e8b1f437 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_internal_test.go @@ -0,0 +1,409 @@ +// This tests the swift package internals +// +// It does not require access to a swift server +// +// FIXME need to add more tests and to check URLs and parameters +package swift + +import ( + "fmt" + "io" + "net" + "net/http" + "testing" + + // "net/http/httputil" + // "os" +) + +const ( + TEST_ADDRESS = "localhost:5324" + AUTH_URL = "http://" + TEST_ADDRESS + "/v1.0" + PROXY_URL = "http://" + TEST_ADDRESS + "/proxy" + USERNAME = "test" + APIKEY = "apikey" + AUTH_TOKEN = "token" +) + +// Globals +var ( + server *SwiftServer + c *Connection +) + +// SwiftServer implements a test swift server +type SwiftServer struct { + t *testing.T + checks []*Check +} + +// Used to check and reply to http transactions +type Check struct { + in Headers + out Headers + rx *string + tx *string + err *Error + url *string +} + +// Add a in check +func (check *Check) In(in Headers) *Check { + check.in = in + return check +} + +// Add an out check +func (check *Check) Out(out Headers) *Check { + check.out = out + return check +} + +// Add an Error check +func (check *Check) Error(StatusCode int, Text string) *Check { + check.err = newError(StatusCode, Text) + return check +} + +// Add a rx check +func (check *Check) Rx(rx string) *Check { + check.rx = &rx + return check +} + +// Add an tx check +func (check *Check) Tx(tx string) *Check { + check.tx = &tx + return check +} + +// Add an URL check +func (check *Check) Url(url string) *Check { + check.url = &url + return check +} + +// Add a check +func (s *SwiftServer) AddCheck(t *testing.T) *Check { + server.t = t + check := &Check{ + in: Headers{}, + out: Headers{}, + err: nil, + } + s.checks = append(s.checks, check) + return check +} + +// Responds to a request +func (s *SwiftServer) Respond(w http.ResponseWriter, r *http.Request) { + if len(s.checks) < 1 { + s.t.Fatal("Unexpected http transaction") + } + check := s.checks[0] + s.checks = s.checks[1:] + + // Check URL + if check.url != nil && *check.url != r.URL.String() { + s.t.Errorf("Expecting URL %q but got %q", *check.url, r.URL) + } + + // Check headers + for k, v := range check.in { + actual := r.Header.Get(k) + if actual != v { + s.t.Errorf("Expecting header %q=%q but got %q", k, v, actual) + } + } + // Write output headers + h := w.Header() + for k, v := range check.out { + h.Set(k, v) + } + // Return an error if required + if check.err != nil { + http.Error(w, check.err.Text, check.err.StatusCode) + } else { + if check.tx != nil { + _, err := w.Write([]byte(*check.tx)) + if err != nil { + s.t.Error("Write failed", err) + } + } + } +} + +// Checks to see all responses are used up +func (s *SwiftServer) Finished() { + if len(s.checks) > 0 { + s.t.Error("Unused checks", s.checks) + } +} + +func handle(w http.ResponseWriter, r *http.Request) { + // out, _ := httputil.DumpRequest(r, true) + // os.Stdout.Write(out) + server.Respond(w, r) +} + +func NewSwiftServer() *SwiftServer { + server := &SwiftServer{} + http.HandleFunc("/", handle) + go http.ListenAndServe(TEST_ADDRESS, nil) + fmt.Print("Waiting for server to start ") + for { + fmt.Print(".") + conn, err := net.Dial("tcp", TEST_ADDRESS) + if err == nil { + conn.Close() + fmt.Println(" Started") + break + } + } + return server +} + +func init() { + server = NewSwiftServer() + c = &Connection{ + UserName: USERNAME, + ApiKey: APIKEY, + AuthUrl: AUTH_URL, + } +} + +// Check the error is a swift error +func checkError(t *testing.T, err error, StatusCode int, Text string) { + if err == nil { + t.Fatal("No error returned") + } + err2, ok := err.(*Error) + if !ok { + t.Fatal("Bad error type") + } + if err2.StatusCode != StatusCode { + t.Fatalf("Bad status code, expecting %d got %d", StatusCode, err2.StatusCode) + } + if err2.Text != Text { + t.Fatalf("Bad error string, expecting %q got %q", Text, err2.Text) + } +} + +// FIXME copied from swift_test.go +func compareMaps(t *testing.T, a, b map[string]string) { + if len(a) != len(b) { + t.Error("Maps different sizes", a, b) + } + for ka, va := range a { + if vb, ok := b[ka]; !ok || va != vb { + t.Error("Difference in key", ka, va, b[ka]) + } + } + for kb, vb := range b { + if va, ok := a[kb]; !ok || vb != va { + t.Error("Difference in key", kb, vb, a[kb]) + } + } +} + +func TestInternalError(t *testing.T) { + e := newError(404, "Not Found!") + if e.StatusCode != 404 || e.Text != "Not Found!" { + t.Fatal("Bad error") + } + if e.Error() != "Not Found!" { + t.Fatal("Bad error") + } + +} + +func testCheckClose(c io.Closer, e error) (err error) { + err = e + defer checkClose(c, &err) + return +} + +// Make a closer which returns the error of our choice +type myCloser struct { + err error +} + +func (c *myCloser) Close() error { + return c.err +} + +func TestInternalCheckClose(t *testing.T) { + if testCheckClose(&myCloser{nil}, nil) != nil { + t.Fatal("bad 1") + } + if testCheckClose(&myCloser{nil}, ObjectCorrupted) != ObjectCorrupted { + t.Fatal("bad 2") + } + if testCheckClose(&myCloser{ObjectNotFound}, nil) != ObjectNotFound { + t.Fatal("bad 3") + } + if testCheckClose(&myCloser{ObjectNotFound}, ObjectCorrupted) != ObjectCorrupted { + t.Fatal("bad 4") + } +} + +func TestInternalParseHeaders(t *testing.T) { + resp := &http.Response{StatusCode: 200} + if c.parseHeaders(resp, nil) != nil { + t.Error("Bad 1") + } + if c.parseHeaders(resp, authErrorMap) != nil { + t.Error("Bad 1") + } + + resp = &http.Response{StatusCode: 299} + if c.parseHeaders(resp, nil) != nil { + t.Error("Bad 1") + } + + resp = &http.Response{StatusCode: 199, Status: "BOOM"} + checkError(t, c.parseHeaders(resp, nil), 199, "HTTP Error: 199: BOOM") + + resp = &http.Response{StatusCode: 300, Status: "BOOM"} + checkError(t, c.parseHeaders(resp, nil), 300, "HTTP Error: 300: BOOM") + + resp = &http.Response{StatusCode: 404, Status: "BOOM"} + checkError(t, c.parseHeaders(resp, nil), 404, "HTTP Error: 404: BOOM") + if c.parseHeaders(resp, ContainerErrorMap) != ContainerNotFound { + t.Error("Bad 1") + } + if c.parseHeaders(resp, objectErrorMap) != ObjectNotFound { + t.Error("Bad 1") + } +} + +func TestInternalReadHeaders(t *testing.T) { + resp := &http.Response{Header: http.Header{}} + compareMaps(t, readHeaders(resp), Headers{}) + + resp = &http.Response{Header: http.Header{ + "one": []string{"1"}, + "two": []string{"2"}, + }} + compareMaps(t, readHeaders(resp), Headers{"one": "1", "two": "2"}) + + // FIXME this outputs a log which we should test and check + resp = &http.Response{Header: http.Header{ + "one": []string{"1", "11", "111"}, + "two": []string{"2"}, + }} + compareMaps(t, readHeaders(resp), Headers{"one": "1", "two": "2"}) +} + +func TestInternalStorage(t *testing.T) { + // FIXME +} + +// ------------------------------------------------------------ + +func TestInternalAuthenticate(t *testing.T) { + server.AddCheck(t).In(Headers{ + "User-Agent": DefaultUserAgent, + "X-Auth-Key": APIKEY, + "X-Auth-User": USERNAME, + }).Out(Headers{ + "X-Storage-Url": PROXY_URL, + "X-Auth-Token": AUTH_TOKEN, + }).Url("/v1.0") + defer server.Finished() + + err := c.Authenticate() + if err != nil { + t.Fatal(err) + } + if c.StorageUrl != PROXY_URL { + t.Error("Bad storage url") + } + if c.AuthToken != AUTH_TOKEN { + t.Error("Bad auth token") + } + if !c.Authenticated() { + t.Error("Didn't authenticate") + } +} + +func TestInternalAuthenticateDenied(t *testing.T) { + server.AddCheck(t).Error(400, "Bad request") + server.AddCheck(t).Error(401, "DENIED") + defer server.Finished() + c.UnAuthenticate() + err := c.Authenticate() + if err != AuthorizationFailed { + t.Fatal("Expecting AuthorizationFailed", err) + } + // FIXME + // if c.Authenticated() { + // t.Fatal("Expecting not authenticated") + // } +} + +func TestInternalAuthenticateBad(t *testing.T) { + server.AddCheck(t).Out(Headers{ + "X-Storage-Url": PROXY_URL, + }) + defer server.Finished() + err := c.Authenticate() + checkError(t, err, 0, "Response didn't have storage url and auth token") + if c.Authenticated() { + t.Fatal("Expecting not authenticated") + } + + server.AddCheck(t).Out(Headers{ + "X-Auth-Token": AUTH_TOKEN, + }) + err = c.Authenticate() + checkError(t, err, 0, "Response didn't have storage url and auth token") + if c.Authenticated() { + t.Fatal("Expecting not authenticated") + } + + server.AddCheck(t) + err = c.Authenticate() + checkError(t, err, 0, "Response didn't have storage url and auth token") + if c.Authenticated() { + t.Fatal("Expecting not authenticated") + } + + server.AddCheck(t).Out(Headers{ + "X-Storage-Url": PROXY_URL, + "X-Auth-Token": AUTH_TOKEN, + }) + err = c.Authenticate() + if err != nil { + t.Fatal(err) + } + if !c.Authenticated() { + t.Fatal("Expecting authenticated") + } +} + +func testContainerNames(t *testing.T, rx string, expected []string) { + server.AddCheck(t).In(Headers{ + "User-Agent": DefaultUserAgent, + "X-Auth-Token": AUTH_TOKEN, + }).Tx(rx).Url("/proxy") + containers, err := c.ContainerNames(nil) + if err != nil { + t.Fatal(err) + } + if len(containers) != len(expected) { + t.Fatal("Wrong number of containers", len(containers), rx, len(expected), expected) + } + for i := range containers { + if containers[i] != expected[i] { + t.Error("Bad container", containers[i], expected[i]) + } + } +} +func TestInternalContainerNames(t *testing.T) { + defer server.Finished() + testContainerNames(t, "", []string{}) + testContainerNames(t, "one", []string{"one"}) + testContainerNames(t, "one\n", []string{"one"}) + testContainerNames(t, "one\ntwo\nthree\n", []string{"one", "two", "three"}) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_test.go new file mode 100644 index 00000000..7e2158fa --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swift_test.go @@ -0,0 +1,1284 @@ +// This tests the swift packagae +// +// It can be used with a real swift server which should be set up in +// the environment variables SWIFT_API_USER, SWIFT_API_KEY and +// SWIFT_AUTH_URL +// In case those variables are not defined, a fake Swift server +// is used instead - see Testing in README.md for more info +// +// The functions are designed to run in order and create things the +// next function tests. This means that if it goes wrong it is likely +// errors will propagate. You may need to tidy up the CONTAINER to +// get it to run cleanly. +package swift_test + +import ( + "archive/tar" + "bytes" + "crypto/md5" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "net/http" + "os" + "sync" + "testing" + "time" + + "github.com/ncw/swift" + "github.com/ncw/swift/swifttest" +) + +var ( + c swift.Connection + srv *swifttest.SwiftServer + m1 = swift.Metadata{"Hello": "1", "potato-Salad": "2"} + m2 = swift.Metadata{"hello": "", "potato-salad": ""} + skipVersionTests = false +) + +const ( + TEST_ADDRESS = "localhost:6543" + AUTH_URL = "http://" + TEST_ADDRESS + "/v1.0" + CONTAINER = "GoSwiftUnitTest" + VERSIONS_CONTAINER = "GoSwiftUnitTestVersions" + CURRENT_CONTAINER = "GoSwiftUnitTestCurrent" + OBJECT = "test_object" + OBJECT2 = "test_object2" + CONTENTS = "12345" + CONTENTS2 = "54321" + CONTENT_SIZE = int64(len(CONTENTS)) + CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b" +) + +type someTransport struct{ http.Transport } + +func TestTransport(t *testing.T) { + var err error + UserName := os.Getenv("SWIFT_API_USER") + ApiKey := os.Getenv("SWIFT_API_KEY") + AuthUrl := os.Getenv("SWIFT_AUTH_URL") + if UserName == "" || ApiKey == "" || AuthUrl == "" { + srv, err = swifttest.NewSwiftServer(TEST_ADDRESS) + if err != nil { + t.Fatal("Failed to create server", err) + } + UserName = "swifttest" + ApiKey = "swifttest" + AuthUrl = AUTH_URL + } + tr := &someTransport{Transport: http.Transport{MaxIdleConnsPerHost: 2048}} + ct := swift.Connection{ + UserName: UserName, + ApiKey: ApiKey, + AuthUrl: AuthUrl, + Tenant: os.Getenv("SWIFT_TENANT"), + TenantId: os.Getenv("SWIFT_TENANT_ID"), + Transport: tr, + ConnectTimeout: 60 * time.Second, + Timeout: 60 * time.Second, + } + err = ct.Authenticate() + if err != nil { + t.Fatal("Auth failed", err) + } + if !ct.Authenticated() { + t.Fatal("Not authenticated") + } + if srv != nil { + srv.Close() + } +} + +// The following Test functions are run in order - this one must come before the others! +func TestAuthenticate(t *testing.T) { + var err error + UserName := os.Getenv("SWIFT_API_USER") + ApiKey := os.Getenv("SWIFT_API_KEY") + AuthUrl := os.Getenv("SWIFT_AUTH_URL") + if UserName == "" || ApiKey == "" || AuthUrl == "" { + srv, err = swifttest.NewSwiftServer(TEST_ADDRESS) + if err != nil { + t.Fatal("Failed to create server", err) + } + UserName = "swifttest" + ApiKey = "swifttest" + AuthUrl = AUTH_URL + } + c = swift.Connection{ + UserName: UserName, + ApiKey: ApiKey, + AuthUrl: AuthUrl, + Tenant: os.Getenv("SWIFT_TENANT"), + TenantId: os.Getenv("SWIFT_TENANT_ID"), + } + err = c.Authenticate() + if err != nil { + t.Fatal("Auth failed", err) + } + if !c.Authenticated() { + t.Fatal("Not authenticated") + } +} + +// Attempt to trigger a race in authenticate +// +// Run with -race to test +func TestAuthenticateRace(t *testing.T) { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + err := c.Authenticate() + if err != nil { + t.Fatal("Auth failed", err) + } + if !c.Authenticated() { + t.Fatal("Not authenticated") + } + }() + } + wg.Wait() +} + +// Test a connection can be serialized and unserialized with JSON +func TestSerializeConnectionJson(t *testing.T) { + serializedConnection, err := json.Marshal(c) + if err != nil { + t.Fatalf("Failed to serialize connection: %v", err) + } + c2 := new(swift.Connection) + err = json.Unmarshal(serializedConnection, &c2) + if err != nil { + t.Fatalf("Failed to unserialize connection: %v", err) + } + if !c2.Authenticated() { + t.Fatal("Should be authenticated") + } + _, _, err = c2.Account() + if err != nil { + t.Fatalf("Failed to use unserialized connection: %v", err) + } +} + +// Test a connection can be serialized and unserialized with XML +func TestSerializeConnectionXml(t *testing.T) { + serializedConnection, err := xml.Marshal(c) + if err != nil { + t.Fatalf("Failed to serialize connection: %v", err) + } + c2 := new(swift.Connection) + err = xml.Unmarshal(serializedConnection, &c2) + if err != nil { + t.Fatalf("Failed to unserialize connection: %v", err) + } + if !c2.Authenticated() { + t.Fatal("Should be authenticated") + } + _, _, err = c2.Account() + if err != nil { + t.Fatalf("Failed to use unserialized connection: %v", err) + } +} + +// Test the reauthentication logic +func TestOnReAuth(t *testing.T) { + c2 := c + c2.UnAuthenticate() + _, _, err := c2.Account() + if err != nil { + t.Fatalf("Failed to reauthenticate: %v", err) + } +} +func TestAccount(t *testing.T) { + info, headers, err := c.Account() + if err != nil { + t.Fatal(err) + } + if headers["X-Account-Container-Count"] != fmt.Sprintf("%d", info.Containers) { + t.Error("Bad container count") + } + if headers["X-Account-Bytes-Used"] != fmt.Sprintf("%d", info.BytesUsed) { + t.Error("Bad bytes count") + } + if headers["X-Account-Object-Count"] != fmt.Sprintf("%d", info.Objects) { + t.Error("Bad objects count") + } + //fmt.Println(info) + //fmt.Println(headers) +} + +func compareMaps(t *testing.T, a, b map[string]string) { + if len(a) != len(b) { + t.Error("Maps different sizes", a, b) + } + for ka, va := range a { + if vb, ok := b[ka]; !ok || va != vb { + t.Error("Difference in key", ka, va, b[ka]) + } + } + for kb, vb := range b { + if va, ok := a[kb]; !ok || vb != va { + t.Error("Difference in key", kb, vb, a[kb]) + } + } +} + +func TestAccountUpdate(t *testing.T) { + err := c.AccountUpdate(m1.AccountHeaders()) + if err != nil { + t.Fatal(err) + } + + _, headers, err := c.Account() + if err != nil { + t.Fatal(err) + } + m := headers.AccountMetadata() + delete(m, "temp-url-key") // remove X-Account-Meta-Temp-URL-Key if set + compareMaps(t, m, map[string]string{"hello": "1", "potato-salad": "2"}) + + err = c.AccountUpdate(m2.AccountHeaders()) + if err != nil { + t.Fatal(err) + } + + _, headers, err = c.Account() + if err != nil { + t.Fatal(err) + } + m = headers.AccountMetadata() + delete(m, "temp-url-key") // remove X-Account-Meta-Temp-URL-Key if set + compareMaps(t, m, map[string]string{}) + + //fmt.Println(c.Account()) + //fmt.Println(headers) + //fmt.Println(headers.AccountMetadata()) + //fmt.Println(c.AccountUpdate(m2.AccountHeaders())) + //fmt.Println(c.Account()) +} + +func TestContainerCreate(t *testing.T) { + err := c.ContainerCreate(CONTAINER, m1.ContainerHeaders()) + if err != nil { + t.Fatal(err) + } +} + +func TestContainer(t *testing.T) { + info, headers, err := c.Container(CONTAINER) + if err != nil { + t.Fatal(err) + } + compareMaps(t, headers.ContainerMetadata(), map[string]string{"hello": "1", "potato-salad": "2"}) + if CONTAINER != info.Name { + t.Error("Bad container count") + } + if headers["X-Container-Bytes-Used"] != fmt.Sprintf("%d", info.Bytes) { + t.Error("Bad bytes count") + } + if headers["X-Container-Object-Count"] != fmt.Sprintf("%d", info.Count) { + t.Error("Bad objects count") + } + //fmt.Println(info) + //fmt.Println(headers) +} + +func TestContainersAll(t *testing.T) { + containers1, err := c.ContainersAll(nil) + if err != nil { + t.Fatal(err) + } + containers2, err := c.Containers(nil) + if err != nil { + t.Fatal(err) + } + if len(containers1) != len(containers2) { + t.Fatal("Wrong length") + } + for i := range containers1 { + if containers1[i] != containers2[i] { + t.Fatal("Not the same") + } + } +} + +func TestContainersAllWithLimit(t *testing.T) { + containers1, err := c.ContainersAll(&swift.ContainersOpts{Limit: 1}) + if err != nil { + t.Fatal(err) + } + containers2, err := c.Containers(nil) + if err != nil { + t.Fatal(err) + } + if len(containers1) != len(containers2) { + t.Fatal("Wrong length") + } + for i := range containers1 { + if containers1[i] != containers2[i] { + t.Fatal("Not the same") + } + } +} + +func TestContainerUpdate(t *testing.T) { + err := c.ContainerUpdate(CONTAINER, m2.ContainerHeaders()) + if err != nil { + t.Fatal(err) + } + _, headers, err := c.Container(CONTAINER) + if err != nil { + t.Fatal(err) + } + compareMaps(t, headers.ContainerMetadata(), map[string]string{}) + //fmt.Println(headers) +} + +func TestContainerNames(t *testing.T) { + containers, err := c.ContainerNames(nil) + if err != nil { + t.Fatal(err) + } + // fmt.Printf("container %q\n", CONTAINER) + ok := false + for _, container := range containers { + if container == CONTAINER { + ok = true + break + } + } + if !ok { + t.Errorf("Didn't find container %q in listing %q", CONTAINER, containers) + } + // fmt.Println(containers) +} + +func TestContainerNamesAll(t *testing.T) { + containers1, err := c.ContainerNamesAll(nil) + if err != nil { + t.Fatal(err) + } + containers2, err := c.ContainerNames(nil) + if err != nil { + t.Fatal(err) + } + if len(containers1) != len(containers2) { + t.Fatal("Wrong length") + } + for i := range containers1 { + if containers1[i] != containers2[i] { + t.Fatal("Not the same") + } + } +} + +func TestContainerNamesAllWithLimit(t *testing.T) { + containers1, err := c.ContainerNamesAll(&swift.ContainersOpts{Limit: 1}) + if err != nil { + t.Fatal(err) + } + containers2, err := c.ContainerNames(nil) + if err != nil { + t.Fatal(err) + } + if len(containers1) != len(containers2) { + t.Fatal("Wrong length") + } + for i := range containers1 { + if containers1[i] != containers2[i] { + t.Fatal("Not the same") + } + } +} + +func TestObjectPutString(t *testing.T) { + err := c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") + if err != nil { + t.Fatal(err) + } + + info, _, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Error(err) + } + if info.ContentType != "application/octet-stream" { + t.Error("Bad content type", info.ContentType) + } + if info.Bytes != CONTENT_SIZE { + t.Error("Bad length") + } + if info.Hash != CONTENT_MD5 { + t.Error("Bad length") + } +} + +func TestObjectPutBytes(t *testing.T) { + err := c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "") + if err != nil { + t.Fatal(err) + } + + info, _, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Error(err) + } + if info.ContentType != "application/octet-stream" { + t.Error("Bad content type", info.ContentType) + } + if info.Bytes != CONTENT_SIZE { + t.Error("Bad length") + } + if info.Hash != CONTENT_MD5 { + t.Error("Bad length") + } +} + +func TestObjectPutMimeType(t *testing.T) { + err := c.ObjectPutString(CONTAINER, "test.jpg", CONTENTS, "") + if err != nil { + t.Fatal(err) + } + + info, _, err := c.Object(CONTAINER, "test.jpg") + if err != nil { + t.Error(err) + } + if info.ContentType != "image/jpeg" { + t.Error("Bad content type", info.ContentType) + } + + // Tidy up + err = c.ObjectDelete(CONTAINER, "test.jpg") + if err != nil { + t.Error(err) + } +} + +func TestObjectCreate(t *testing.T) { + out, err := c.ObjectCreate(CONTAINER, OBJECT2, true, "", "", nil) + if err != nil { + t.Fatal(err) + } + buf := &bytes.Buffer{} + hash := md5.New() + out2 := io.MultiWriter(out, buf, hash) + for i := 0; i < 100; i++ { + fmt.Fprintf(out2, "%d %s\n", i, CONTENTS) + } + err = out.Close() + if err != nil { + t.Error(err) + } + expected := buf.String() + contents, err := c.ObjectGetString(CONTAINER, OBJECT2) + if err != nil { + t.Error(err) + } + if contents != expected { + t.Error("Contents wrong") + } + + // Test writing on closed file + n, err := out.Write([]byte{0}) + if err == nil || n != 0 { + t.Error("Expecting error and n == 0 writing on closed file", err, n) + } + + // Now with hash instead + out, err = c.ObjectCreate(CONTAINER, OBJECT2, false, fmt.Sprintf("%x", hash.Sum(nil)), "", nil) + if err != nil { + t.Fatal(err) + } + _, err = out.Write(buf.Bytes()) + if err != nil { + t.Error(err) + } + err = out.Close() + if err != nil { + t.Error(err) + } + contents, err = c.ObjectGetString(CONTAINER, OBJECT2) + if err != nil { + t.Error(err) + } + if contents != expected { + t.Error("Contents wrong") + } + + // Now with bad hash + out, err = c.ObjectCreate(CONTAINER, OBJECT2, false, CONTENT_MD5, "", nil) + if err != nil { + t.Fatal(err) + } + // FIXME: work around bug which produces 503 not 422 for empty corrupted files + fmt.Fprintf(out, "Sausage") + err = out.Close() + if err != swift.ObjectCorrupted { + t.Error("Expecting object corrupted not", err) + } + + // Tidy up + err = c.ObjectDelete(CONTAINER, OBJECT2) + if err != nil { + t.Error(err) + } +} + +func TestObjectGetString(t *testing.T) { + contents, err := c.ObjectGetString(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + if contents != CONTENTS { + t.Error("Contents wrong") + } + //fmt.Println(contents) +} + +func TestObjectGetBytes(t *testing.T) { + contents, err := c.ObjectGetBytes(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + if string(contents) != CONTENTS { + t.Error("Contents wrong") + } + //fmt.Println(contents) +} + +func TestObjectOpen(t *testing.T) { + file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) + if err != nil { + t.Fatal(err) + } + var buf bytes.Buffer + n, err := io.Copy(&buf, file) + if err != nil { + t.Fatal(err) + } + if n != CONTENT_SIZE { + t.Fatal("Wrong length", n, CONTENT_SIZE) + } + if buf.String() != CONTENTS { + t.Error("Contents wrong") + } + err = file.Close() + if err != nil { + t.Fatal(err) + } +} + +func TestObjectOpenPartial(t *testing.T) { + file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) + if err != nil { + t.Fatal(err) + } + var buf bytes.Buffer + n, err := io.CopyN(&buf, file, 1) + if err != nil { + t.Fatal(err) + } + if n != 1 { + t.Fatal("Wrong length", n, CONTENT_SIZE) + } + if buf.String() != CONTENTS[:1] { + t.Error("Contents wrong") + } + err = file.Close() + if err != nil { + t.Fatal(err) + } +} + +func TestObjectOpenLength(t *testing.T) { + file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) + if err != nil { + t.Fatal(err) + } + // FIXME ideally this would check both branches of the Length() code + n, err := file.Length() + if err != nil { + t.Fatal(err) + } + if n != CONTENT_SIZE { + t.Fatal("Wrong length", n, CONTENT_SIZE) + } + err = file.Close() + if err != nil { + t.Fatal(err) + } +} + +func TestObjectOpenSeek(t *testing.T) { + + plan := []struct { + whence int + offset int64 + result int64 + }{ + {-1, 0, 0}, + {-1, 0, 1}, + {-1, 0, 2}, + {0, 0, 0}, + {0, 0, 0}, + {0, 1, 1}, + {0, 2, 2}, + {1, 0, 3}, + {1, -2, 2}, + {1, 1, 4}, + {2, -1, 4}, + {2, -3, 2}, + {2, -2, 3}, + {2, -5, 0}, + {2, -4, 1}, + } + + file, _, err := c.ObjectOpen(CONTAINER, OBJECT, true, nil) + if err != nil { + t.Fatal(err) + } + + for _, p := range plan { + if p.whence >= 0 { + result, err := file.Seek(p.offset, p.whence) + if err != nil { + t.Fatal(err, p) + } + if result != p.result { + t.Fatal("Seek result was", result, "expecting", p.result, p) + } + + } + var buf bytes.Buffer + n, err := io.CopyN(&buf, file, 1) + if err != nil { + t.Fatal(err, p) + } + if n != 1 { + t.Fatal("Wrong length", n, p) + } + actual := buf.String() + expected := CONTENTS[p.result : p.result+1] + if actual != expected { + t.Error("Contents wrong, expecting", expected, "got", actual, p) + } + } + + err = file.Close() + if err != nil { + t.Fatal(err) + } +} + +func TestObjectUpdate(t *testing.T) { + err := c.ObjectUpdate(CONTAINER, OBJECT, m1.ObjectHeaders()) + if err != nil { + t.Fatal(err) + } +} + +func checkTime(t *testing.T, when time.Time, low, high int) { + dt := time.Now().Sub(when) + if dt < time.Duration(low)*time.Second || dt > time.Duration(high)*time.Second { + t.Errorf("Time is wrong: dt=%q, when=%q", dt, when) + } +} + +func TestObject(t *testing.T) { + object, headers, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + compareMaps(t, headers.ObjectMetadata(), map[string]string{"hello": "1", "potato-salad": "2"}) + if object.Name != OBJECT || object.Bytes != CONTENT_SIZE || object.ContentType != "application/octet-stream" || object.Hash != CONTENT_MD5 || object.PseudoDirectory != false || object.SubDir != "" { + t.Error("Bad object info", object) + } + checkTime(t, object.LastModified, -10, 10) +} + +func TestObjectUpdate2(t *testing.T) { + err := c.ObjectUpdate(CONTAINER, OBJECT, m2.ObjectHeaders()) + if err != nil { + t.Fatal(err) + } + _, headers, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + //fmt.Println(headers, headers.ObjectMetadata()) + compareMaps(t, headers.ObjectMetadata(), map[string]string{"hello": "", "potato-salad": ""}) +} + +func TestContainers(t *testing.T) { + containers, err := c.Containers(nil) + if err != nil { + t.Fatal(err) + } + ok := false + for _, container := range containers { + if container.Name == CONTAINER { + ok = true + // Container may or may not have the file contents in it + // Swift updates may be behind + if container.Count == 0 && container.Bytes == 0 { + break + } + if container.Count == 1 && container.Bytes == CONTENT_SIZE { + break + } + t.Errorf("Bad size of Container %q: %q", CONTAINER, container) + break + } + } + if !ok { + t.Errorf("Didn't find container %q in listing %q", CONTAINER, containers) + } + //fmt.Println(containers) +} + +func TestObjectNames(t *testing.T) { + objects, err := c.ObjectNames(CONTAINER, nil) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0] != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjectNamesAll(t *testing.T) { + objects, err := c.ObjectNamesAll(CONTAINER, nil) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0] != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjectNamesAllWithLimit(t *testing.T) { + objects, err := c.ObjectNamesAll(CONTAINER, &swift.ObjectsOpts{Limit: 1}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0] != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjectsWalk(t *testing.T) { + objects := make([]string, 0) + err := c.ObjectsWalk(container, nil, func(opts *swift.ObjectsOpts) (interface{}, error) { + newObjects, err := c.ObjectNames(CONTAINER, opts) + if err == nil { + objects = append(objects, newObjects...) + } + return newObjects, err + }) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0] != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjects(t *testing.T) { + objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 { + t.Fatal("Should only be 1 object") + } + object := objects[0] + if object.Name != OBJECT || object.Bytes != CONTENT_SIZE || object.ContentType != "application/octet-stream" || object.Hash != CONTENT_MD5 || object.PseudoDirectory != false || object.SubDir != "" { + t.Error("Bad object info", object) + } + checkTime(t, object.LastModified, -10, 10) + // fmt.Println(objects) +} + +func TestObjectsDirectory(t *testing.T) { + err := c.ObjectPutString(CONTAINER, "directory", "", "application/directory") + if err != nil { + t.Fatal(err) + } + defer c.ObjectDelete(CONTAINER, "directory") + + // Look for the directory object and check we aren't confusing + // it with a pseudo directory object + objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 2 { + t.Fatal("Should only be 2 objects") + } + found := false + for i := range objects { + object := objects[i] + if object.Name == "directory" { + found = true + if object.Bytes != 0 || object.ContentType != "application/directory" || object.Hash != "d41d8cd98f00b204e9800998ecf8427e" || object.PseudoDirectory != false || object.SubDir != "" { + t.Error("Bad object info", object) + } + checkTime(t, object.LastModified, -10, 10) + } + } + if !found { + t.Error("Didn't find directory object") + } + // fmt.Println(objects) +} + +func TestObjectsPseudoDirectory(t *testing.T) { + err := c.ObjectPutString(CONTAINER, "directory/puppy.jpg", "cute puppy", "") + if err != nil { + t.Fatal(err) + } + defer c.ObjectDelete(CONTAINER, "directory/puppy.jpg") + + // Look for the pseudo directory + objects, err := c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/'}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 2 { + t.Fatal("Should only be 2 objects", objects) + } + found := false + for i := range objects { + object := objects[i] + if object.Name == "directory/" { + found = true + if object.Bytes != 0 || object.ContentType != "application/directory" || object.Hash != "" || object.PseudoDirectory != true || object.SubDir != "directory/" && object.LastModified.IsZero() { + t.Error("Bad object info", object) + } + } + } + if !found { + t.Error("Didn't find directory object", objects) + } + + // Look in the pseudo directory now + objects, err = c.Objects(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "directory/"}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 { + t.Fatal("Should only be 1 object", objects) + } + object := objects[0] + if object.Name != "directory/puppy.jpg" || object.Bytes != 10 || object.ContentType != "image/jpeg" || object.Hash != "87a12ea22fca7f54f0cefef1da535489" || object.PseudoDirectory != false || object.SubDir != "" { + t.Error("Bad object info", object) + } + checkTime(t, object.LastModified, -10, 10) + // fmt.Println(objects) +} + +func TestObjectsAll(t *testing.T) { + objects, err := c.ObjectsAll(CONTAINER, nil) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0].Name != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjectsAllWithLimit(t *testing.T) { + objects, err := c.ObjectsAll(CONTAINER, &swift.ObjectsOpts{Limit: 1}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0].Name != OBJECT { + t.Error("Incorrect listing", objects) + } + //fmt.Println(objects) +} + +func TestObjectNamesWithPath(t *testing.T) { + objects, err := c.ObjectNames(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: ""}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 1 || objects[0] != OBJECT { + t.Error("Bad listing with path", objects) + } + // fmt.Println(objects) + objects, err = c.ObjectNames(CONTAINER, &swift.ObjectsOpts{Delimiter: '/', Path: "Downloads/"}) + if err != nil { + t.Fatal(err) + } + if len(objects) != 0 { + t.Error("Bad listing with path", objects) + } + // fmt.Println(objects) +} + +func TestObjectCopy(t *testing.T) { + _, err := c.ObjectCopy(CONTAINER, OBJECT, CONTAINER, OBJECT2, nil) + if err != nil { + t.Fatal(err) + } + err = c.ObjectDelete(CONTAINER, OBJECT2) + if err != nil { + t.Fatal(err) + } +} + +func TestObjectCopyWithMetadata(t *testing.T) { + m := swift.Metadata{} + m["copy-special-metadata"] = "hello" + m["hello"] = "3" + h := m.ObjectHeaders() + h["Content-Type"] = "image/jpeg" + _, err := c.ObjectCopy(CONTAINER, OBJECT, CONTAINER, OBJECT2, h) + if err != nil { + t.Fatal(err) + } + // Re-read the metadata to see if it is correct + _, headers, err := c.Object(CONTAINER, OBJECT2) + if err != nil { + t.Fatal(err) + } + if headers["Content-Type"] != "image/jpeg" { + t.Error("Didn't change content type") + } + compareMaps(t, headers.ObjectMetadata(), map[string]string{"hello": "3", "potato-salad": "", "copy-special-metadata": "hello"}) + err = c.ObjectDelete(CONTAINER, OBJECT2) + if err != nil { + t.Fatal(err) + } +} + +func TestObjectMove(t *testing.T) { + err := c.ObjectMove(CONTAINER, OBJECT, CONTAINER, OBJECT2) + if err != nil { + t.Fatal(err) + } + testExistenceAfterDelete(t, CONTAINER, OBJECT) + _, _, err = c.Object(CONTAINER, OBJECT2) + if err != nil { + t.Fatal(err) + } + + err = c.ObjectMove(CONTAINER, OBJECT2, CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + testExistenceAfterDelete(t, CONTAINER, OBJECT2) + _, headers, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + compareMaps(t, headers.ObjectMetadata(), map[string]string{"hello": "", "potato-salad": ""}) +} + +func TestObjectUpdateContentType(t *testing.T) { + err := c.ObjectUpdateContentType(CONTAINER, OBJECT, "text/potato") + if err != nil { + t.Fatal(err) + } + // Re-read the metadata to see if it is correct + _, headers, err := c.Object(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + if headers["Content-Type"] != "text/potato" { + t.Error("Didn't change content type") + } + compareMaps(t, headers.ObjectMetadata(), map[string]string{"hello": "", "potato-salad": ""}) +} + +func TestVersionContainerCreate(t *testing.T) { + if err := c.VersionContainerCreate(CURRENT_CONTAINER, VERSIONS_CONTAINER); err != nil { + if err == swift.Forbidden { + t.Log("Server doesn't support Versions - skipping test") + skipVersionTests = true + return + } + t.Fatal(err) + } +} + +func TestVersionObjectAdd(t *testing.T) { + if skipVersionTests { + t.Log("Server doesn't support Versions - skipping test") + return + } + // Version 1 + if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS, ""); err != nil { + t.Fatal(err) + } + if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } else if contents != CONTENTS { + t.Error("Contents wrong") + } + + // Version 2 + if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { + t.Fatal(err) + } + if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } else if contents != CONTENTS2 { + t.Error("Contents wrong") + } + + // Version 3 + if err := c.ObjectPutString(CURRENT_CONTAINER, OBJECT, CONTENTS2, ""); err != nil { + t.Fatal(err) + } +} + +func TestVersionObjectList(t *testing.T) { + if skipVersionTests { + t.Log("Server doesn't support Versions - skipping test") + return + } + list, err := c.VersionObjectList(VERSIONS_CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + + if len(list) != 2 { + t.Error("Version list should return 2 objects") + } + + //fmt.Print(list) +} + +func TestVersionObjectDelete(t *testing.T) { + if skipVersionTests { + t.Log("Server doesn't support Versions - skipping test") + return + } + // Delete Version 3 + if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } + + // Delete Version 2 + if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } + + // Contents should be reverted to Version 1 + if contents, err := c.ObjectGetString(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } else if contents != CONTENTS { + t.Error("Contents wrong") + } +} + +// cleanUpContainer deletes everything in the container and then the +// container. It expects the container to be empty and if it wasn't +// it logs an error. +func cleanUpContainer(t *testing.T, container string) { + objects, err := c.Objects(container, nil) + if err != nil { + t.Error(err, container) + } else { + if len(objects) != 0 { + t.Error("Container not empty", container) + } + for _, object := range objects { + t.Log("Deleting spurious", object.Name) + err = c.ObjectDelete(container, object.Name) + if err != nil { + t.Error(err, container) + } + } + } + + if err := c.ContainerDelete(container); err != nil { + t.Error(err, container) + } +} + +func TestVersionDeleteContent(t *testing.T) { + if skipVersionTests { + t.Log("Server doesn't support Versions - skipping test") + } else { + // Delete Version 1 + if err := c.ObjectDelete(CURRENT_CONTAINER, OBJECT); err != nil { + t.Fatal(err) + } + } + cleanUpContainer(t, VERSIONS_CONTAINER) + cleanUpContainer(t, CURRENT_CONTAINER) +} + +// Check for non existence after delete +// May have to do it a few times to wait for swift to be consistent. +func testExistenceAfterDelete(t *testing.T, container, object string) { + for i := 10; i <= 0; i-- { + _, _, err := c.Object(container, object) + if err == swift.ObjectNotFound { + break + } + if i == 0 { + t.Fatalf("Expecting object %q/%q not found not: err=%v", container, object, err) + } + time.Sleep(1 * time.Second) + } +} + +func TestObjectDelete(t *testing.T) { + err := c.ObjectDelete(CONTAINER, OBJECT) + if err != nil { + t.Fatal(err) + } + testExistenceAfterDelete(t, CONTAINER, OBJECT) + err = c.ObjectDelete(CONTAINER, OBJECT) + if err != swift.ObjectNotFound { + t.Fatal("Expecting Object not found", err) + } +} + +func TestBulkDelete(t *testing.T) { + result, err := c.BulkDelete(CONTAINER, []string{OBJECT}) + if err == swift.Forbidden { + t.Log("Server doesn't support BulkDelete - skipping test") + return + } + if err != nil { + t.Fatal(err) + } + if result.NumberNotFound != 1 { + t.Error("Expected 1, actual:", result.NumberNotFound) + } + if result.NumberDeleted != 0 { + t.Error("Expected 0, actual:", result.NumberDeleted) + } + err = c.ObjectPutString(CONTAINER, OBJECT, CONTENTS, "") + if err != nil { + t.Fatal(err) + } + result, err = c.BulkDelete(CONTAINER, []string{OBJECT2, OBJECT}) + if err != nil { + t.Fatal(err) + } + if result.NumberNotFound != 1 { + t.Error("Expected 1, actual:", result.NumberNotFound) + } + if result.NumberDeleted != 1 { + t.Error("Expected 1, actual:", result.NumberDeleted) + } + t.Log("Errors:", result.Errors) +} + +func TestBulkUpload(t *testing.T) { + buffer := new(bytes.Buffer) + ds := tar.NewWriter(buffer) + var files = []struct{ Name, Body string }{ + {OBJECT, CONTENTS}, + {OBJECT2, CONTENTS2}, + } + for _, file := range files { + hdr := &tar.Header{ + Name: file.Name, + Size: int64(len(file.Body)), + } + if err := ds.WriteHeader(hdr); err != nil { + t.Fatal(err) + } + if _, err := ds.Write([]byte(file.Body)); err != nil { + t.Fatal(err) + } + } + if err := ds.Close(); err != nil { + t.Fatal(err) + } + + result, err := c.BulkUpload(CONTAINER, buffer, swift.UploadTar, nil) + if err == swift.Forbidden { + t.Log("Server doesn't support BulkUpload - skipping test") + return + } + if err != nil { + t.Fatal(err) + } + if result.NumberCreated != 2 { + t.Error("Expected 2, actual:", result.NumberCreated) + } + t.Log("Errors:", result.Errors) + + _, _, err = c.Object(CONTAINER, OBJECT) + if err != nil { + t.Error("Expecting object to be found") + } + _, _, err = c.Object(CONTAINER, OBJECT2) + if err != nil { + t.Error("Expecting object to be found") + } + c.ObjectDelete(CONTAINER, OBJECT) + c.ObjectDelete(CONTAINER, OBJECT2) +} + +func TestObjectDifficultName(t *testing.T) { + const name = `hello? sausage/êé/Hello, 世界/ " ' @ < > & ?/` + err := c.ObjectPutString(CONTAINER, name, CONTENTS, "") + if err != nil { + t.Fatal(err) + } + objects, err := c.ObjectNamesAll(CONTAINER, nil) + if err != nil { + t.Error(err) + } + found := false + for _, object := range objects { + if object == name { + found = true + break + } + } + if !found { + t.Errorf("Couldn't find %q in listing %q", name, objects) + } + err = c.ObjectDelete(CONTAINER, name) + if err != nil { + t.Fatal(err) + } +} + +func TestContainerDelete(t *testing.T) { + err := c.ContainerDelete(CONTAINER) + if err != nil { + t.Fatal(err) + } + err = c.ContainerDelete(CONTAINER) + if err != swift.ContainerNotFound { + t.Fatal("Expecting container not found", err) + } + _, _, err = c.Container(CONTAINER) + if err != swift.ContainerNotFound { + t.Fatal("Expecting container not found", err) + } +} + +func TestUnAuthenticate(t *testing.T) { + c.UnAuthenticate() + if c.Authenticated() { + t.Fatal("Shouldn't be authenticated") + } + // Test re-authenticate + err := c.Authenticate() + if err != nil { + t.Fatal("ReAuth failed", err) + } + if !c.Authenticated() { + t.Fatal("Not authenticated") + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swifttest/server.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swifttest/server.go new file mode 100644 index 00000000..abdd170e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/swifttest/server.go @@ -0,0 +1,806 @@ +// This implements a very basic Swift server +// Everything is stored in memory +// +// This comes from the https://github.com/mitchellh/goamz +// and was adapted for Swift +// +package swifttest + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "mime" + "net" + "net/http" + "net/url" + "path" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/ncw/swift" +) + +const ( + DEBUG = false +) + +type SwiftServer struct { + t *testing.T + listener net.Listener + url string + reqId int + mu sync.Mutex + containers map[string]*container + accounts map[string]*account + sessions map[string]*session +} + +// The Folder type represents a container stored in an account +type Folder struct { + Count int `json:"count"` + Bytes int `json:"bytes"` + Name string `json:"name"` +} + +// The Key type represents an item stored in an container. +type Key struct { + Key string `json:"name"` + LastModified string `json:"last_modified"` + Size int64 `json:"bytes"` + // ETag gives the hex-encoded MD5 sum of the contents, + // surrounded with double-quotes. + ETag string `json:"hash"` + ContentType string `json:"content_type"` + // Owner Owner +} + +type Subdir struct { + Subdir string `json:"subdir"` +} + +type swiftError struct { + statusCode int + Code string + Message string +} + +type action struct { + srv *SwiftServer + w http.ResponseWriter + req *http.Request + reqId string + user *account +} + +type session struct { + username string +} + +type metadata struct { + meta http.Header // metadata to return with requests. +} + +type account struct { + swift.Account + metadata + password string +} + +type object struct { + metadata + name string + mtime time.Time + checksum []byte // also held as ETag in meta. + data []byte + content_type string +} + +type container struct { + metadata + name string + ctime time.Time + objects map[string]*object + bytes int +} + +// A resource encapsulates the subject of an HTTP request. +// The resource referred to may or may not exist +// when the request is made. +type resource interface { + put(a *action) interface{} + get(a *action) interface{} + post(a *action) interface{} + delete(a *action) interface{} + copy(a *action) interface{} +} + +type objectResource struct { + name string + version string + container *container // always non-nil. + object *object // may be nil. +} + +type containerResource struct { + name string + container *container // non-nil if the container already exists. +} + +var responseParams = map[string]bool{ + "content-type": true, + "content-language": true, + "expires": true, + "cache-control": true, + "content-disposition": true, + "content-encoding": true, +} + +func fatalf(code int, codeStr string, errf string, a ...interface{}) { + panic(&swiftError{ + statusCode: code, + Code: codeStr, + Message: fmt.Sprintf(errf, a...), + }) +} + +func (m metadata) setMetadata(a *action, resource string) { + for key, values := range a.req.Header { + key = http.CanonicalHeaderKey(key) + if metaHeaders[key] || strings.HasPrefix(key, "X-"+strings.Title(resource)+"-Meta-") { + if values[0] != "" || resource == "object" { + m.meta[key] = values + } else { + m.meta.Del(key) + } + } + } +} + +func (m metadata) getMetadata(a *action) { + h := a.w.Header() + for name, d := range m.meta { + h[name] = d + } +} + +// GET on a bucket lists the objects in the bucket. +func (r containerResource) get(a *action) interface{} { + if r.container == nil { + fatalf(404, "NoSuchContainer", "The specified container does not exist") + } + + delimiter := a.req.Form.Get("delimiter") + marker := a.req.Form.Get("marker") + prefix := a.req.Form.Get("prefix") + format := a.req.URL.Query().Get("format") + parent := a.req.Form.Get("path") + + a.w.Header().Set("X-Container-Bytes-Used", strconv.Itoa(r.container.bytes)) + a.w.Header().Set("X-Container-Object-Count", strconv.Itoa(len(r.container.objects))) + r.container.getMetadata(a) + + if a.req.Method == "HEAD" { + return nil + } + + var tmp orderedObjects + var resp []interface{} = make([]interface{}, 0) + + // first get all matching objects and arrange them in alphabetical order. + for _, obj := range r.container.objects { + if strings.HasPrefix(obj.name, prefix) { + tmp = append(tmp, obj) + } + } + sort.Sort(tmp) + + var prefixes []string + for _, obj := range tmp { + if !strings.HasPrefix(obj.name, prefix) { + continue + } + + isPrefix := false + name := obj.name + if parent != "" { + if path.Dir(obj.name) != path.Clean(parent) { + continue + } + } else if delimiter != "" { + if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 { + name = obj.name[:len(prefix)+i+len(delimiter)] + if prefixes != nil && prefixes[len(prefixes)-1] == name { + continue + } + isPrefix = true + } + } + + if name <= marker { + continue + } + + if isPrefix { + prefixes = append(prefixes, name) + resp = append(resp, Subdir{ + Subdir: name, + }) + } else { + if format == "json" { + resp = append(resp, obj.Key()) + } else { + a.w.Write([]byte(obj.name + "\n")) + } + } + } + + if format == "json" { + return resp + } else { + return nil + } +} + +// orderedContainers holds a slice of containers that can be sorted +// by name. +type orderedContainers []*container + +func (s orderedContainers) Len() int { + return len(s) +} +func (s orderedContainers) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s orderedContainers) Less(i, j int) bool { + return s[i].name < s[j].name +} + +func (r containerResource) delete(a *action) interface{} { + b := r.container + if b == nil { + fatalf(404, "NoSuchContainer", "The specified container does not exist") + } + if len(b.objects) > 0 { + fatalf(409, "Conflict", "The container you tried to delete is not empty") + } + delete(a.srv.containers, b.name) + a.user.Containers-- + return nil +} + +func (r containerResource) put(a *action) interface{} { + if a.req.URL.Query().Get("extract-archive") != "" { + fatalf(403, "Operation forbidden", "Bulk upload is not supported") + } + + var created bool + if r.container == nil { + if !validContainerName(r.name) { + fatalf(400, "InvalidContainerName", "The specified container is not valid") + } + r.container = &container{ + name: r.name, + objects: make(map[string]*object), + metadata: metadata{ + meta: make(http.Header), + }, + } + r.container.setMetadata(a, "container") + a.srv.containers[r.name] = r.container + a.user.Containers++ + created = true + } + if !created { + fatalf(409, "ContainerAlreadyOwnedByYou", "Your previous request to create the named container succeeded and you already own it.") + } + return nil +} + +func (r containerResource) post(a *action) interface{} { + if r.container == nil { + fatalf(400, "Method", "The resource could not be found.") + } else { + r.container.setMetadata(a, "container") + a.w.WriteHeader(201) + jsonMarshal(a.w, Folder{ + Count: len(r.container.objects), + Bytes: r.container.bytes, + Name: r.container.name, + }) + } + return nil +} + +func (containerResource) copy(a *action) interface{} { return notAllowed() } + +// validContainerName returns whether name is a valid bucket name. +// Here are the rules, from: +// http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-api-storage.html +// +// Container names cannot exceed 256 bytes and cannot contain the / character. +// +func validContainerName(name string) bool { + if len(name) == 0 || len(name) > 256 { + return false + } + for _, r := range name { + switch { + case r == '/': + return false + default: + } + } + return true +} + +// orderedObjects holds a slice of objects that can be sorted +// by name. +type orderedObjects []*object + +func (s orderedObjects) Len() int { + return len(s) +} +func (s orderedObjects) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s orderedObjects) Less(i, j int) bool { + return s[i].name < s[j].name +} + +func (obj *object) Key() Key { + return Key{ + Key: obj.name, + LastModified: obj.mtime.Format("2006-01-02T15:04:05"), + Size: int64(len(obj.data)), + ETag: fmt.Sprintf("%x", obj.checksum), + ContentType: obj.content_type, + } +} + +var metaHeaders = map[string]bool{ + "Content-Type": true, + "Content-Encoding": true, + "Content-Disposition": true, +} + +var rangeRegexp = regexp.MustCompile("([0-9])*-([0-9])*") + +// GET on an object gets the contents of the object. +func (objr objectResource) get(a *action) interface{} { + obj := objr.object + if obj == nil { + fatalf(404, "Not Found", "The resource could not be found.") + } + h := a.w.Header() + // add metadata + obj.getMetadata(a) + + var ( + start int + end int = len(obj.data) + ) + if r := a.req.Header.Get("Range"); r != "" { + m := rangeRegexp.FindStringSubmatch(r) + if m[1] != "" { + start, _ = strconv.Atoi(m[1]) + } + if m[2] != "" { + end, _ = strconv.Atoi(m[2]) + } + } + + h.Set("Content-Length", fmt.Sprint(len(obj.data))) + h.Set("ETag", hex.EncodeToString(obj.checksum)) + h.Set("Last-Modified", obj.mtime.Format(http.TimeFormat)) + + if a.req.Method == "HEAD" { + return nil + } + + // TODO avoid holding the lock when writing data. + _, err := a.w.Write(obj.data[start:end]) + if err != nil { + // we can't do much except just log the fact. + log.Printf("error writing data: %v", err) + } + return nil +} + +// PUT on an object creates the object. +func (objr objectResource) put(a *action) interface{} { + var expectHash []byte + if c := a.req.Header.Get("ETag"); c != "" { + var err error + expectHash, err = hex.DecodeString(c) + if err != nil || len(expectHash) != md5.Size { + fatalf(400, "InvalidDigest", "The ETag you specified was invalid") + } + } + sum := md5.New() + // TODO avoid holding lock while reading data. + data, err := ioutil.ReadAll(io.TeeReader(a.req.Body, sum)) + if err != nil { + fatalf(400, "TODO", "read error") + } + gotHash := sum.Sum(nil) + if expectHash != nil && bytes.Compare(gotHash, expectHash) != 0 { + fatalf(422, "Bad ETag", "The ETag you specified did not match what we received") + } + if a.req.ContentLength >= 0 && int64(len(data)) != a.req.ContentLength { + fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header") + } + + // TODO is this correct, or should we erase all previous metadata? + obj := objr.object + if obj == nil { + obj = &object{ + name: objr.name, + metadata: metadata{ + meta: make(http.Header), + }, + } + a.user.Objects++ + } else { + objr.container.bytes -= len(obj.data) + a.user.BytesUsed -= int64(len(obj.data)) + } + + var content_type string + if content_type = a.req.Header.Get("Content-Type"); content_type == "" { + content_type := mime.TypeByExtension(obj.name) + if content_type == "" { + content_type = "application/octet-stream" + } + } + + // PUT request has been successful - save data and metadata + obj.setMetadata(a, "object") + obj.content_type = content_type + obj.data = data + obj.checksum = gotHash + obj.mtime = time.Now().UTC() + objr.container.objects[objr.name] = obj + objr.container.bytes += len(data) + a.user.BytesUsed += int64(len(data)) + + h := a.w.Header() + h.Set("ETag", hex.EncodeToString(obj.checksum)) + + return nil +} + +func (objr objectResource) delete(a *action) interface{} { + if objr.object == nil { + fatalf(404, "NoSuchKey", "The specified key does not exist.") + } + + objr.container.bytes -= len(objr.object.data) + a.user.BytesUsed -= int64(len(objr.object.data)) + delete(objr.container.objects, objr.name) + a.user.Objects-- + return nil +} + +func (objr objectResource) post(a *action) interface{} { + obj := objr.object + obj.setMetadata(a, "object") + return nil +} + +func (objr objectResource) copy(a *action) interface{} { + obj := objr.object + destination := a.req.Header.Get("Destination") + if destination == "" { + fatalf(400, "Bad Request", "You must provide a Destination header") + } + + var ( + obj2 *object + objr2 objectResource + ) + + destURL, _ := url.Parse("/v1/AUTH_tk/" + destination) + r := a.srv.resourceForURL(destURL) + switch t := r.(type) { + case objectResource: + objr2 = t + if objr2.object == nil { + obj2 = &object{ + name: objr2.name, + metadata: metadata{ + meta: make(http.Header), + }, + } + a.user.Objects++ + } else { + obj2 = objr2.object + objr2.container.bytes -= len(obj2.data) + a.user.BytesUsed -= int64(len(obj2.data)) + } + default: + fatalf(400, "Bad Request", "Destination must point to a valid object path") + } + + obj2.content_type = obj.content_type + obj2.data = obj.data + obj2.checksum = obj.checksum + obj2.mtime = time.Now() + objr2.container.objects[objr2.name] = obj2 + objr2.container.bytes += len(obj.data) + a.user.BytesUsed += int64(len(obj.data)) + + for key, values := range obj.metadata.meta { + obj2.metadata.meta[key] = values + } + obj2.setMetadata(a, "object") + + return nil +} + +func (s *SwiftServer) serveHTTP(w http.ResponseWriter, req *http.Request) { + // ignore error from ParseForm as it's usually spurious. + req.ParseForm() + + s.mu.Lock() + defer s.mu.Unlock() + + if DEBUG { + log.Printf("swifttest %q %q", req.Method, req.URL) + } + a := &action{ + srv: s, + w: w, + req: req, + reqId: fmt.Sprintf("%09X", s.reqId), + } + s.reqId++ + + var r resource + defer func() { + switch err := recover().(type) { + case *swiftError: + w.Header().Set("Content-Type", `text/plain; charset=utf-8`) + http.Error(w, err.Message, err.statusCode) + case nil: + default: + panic(err) + } + }() + + var resp interface{} + + if req.URL.String() == "/v1.0" { + username := req.Header.Get("x-auth-user") + key := req.Header.Get("x-auth-key") + if acct, ok := s.accounts[username]; ok { + if acct.password == key { + r := make([]byte, 16) + _, _ = rand.Read(r) + id := fmt.Sprintf("%X", r) + w.Header().Set("X-Storage-Url", s.url+"/v1/AUTH_"+username) + w.Header().Set("X-Auth-Token", "AUTH_tk"+string(id)) + w.Header().Set("X-Storage-Token", "AUTH_tk"+string(id)) + s.sessions[id] = &session{ + username: username, + } + return + } + } + panic(notAuthorized()) + } + + key := req.Header.Get("x-auth-token") + session, ok := s.sessions[key[7:]] + if !ok { + panic(notAuthorized()) + } + + a.user = s.accounts[session.username] + + r = s.resourceForURL(req.URL) + + switch req.Method { + case "PUT": + resp = r.put(a) + case "GET", "HEAD": + resp = r.get(a) + case "DELETE": + resp = r.delete(a) + case "POST": + resp = r.post(a) + case "COPY": + resp = r.copy(a) + default: + fatalf(400, "MethodNotAllowed", "unknown http request method %q", req.Method) + } + + content_type := req.Header.Get("Content-Type") + if resp != nil && req.Method != "HEAD" { + if strings.HasPrefix(content_type, "application/json") || + req.URL.Query().Get("format") == "json" { + jsonMarshal(w, resp) + } else { + switch r := resp.(type) { + case string: + w.Write([]byte(r)) + default: + w.Write(resp.([]byte)) + } + } + } +} + +func jsonMarshal(w io.Writer, x interface{}) { + if err := json.NewEncoder(w).Encode(x); err != nil { + panic(fmt.Errorf("error marshalling %#v: %v", x, err)) + } +} + +var pathRegexp = regexp.MustCompile("/v1/AUTH_[a-zA-Z0-9]+(/([^/]+)(/(.*))?)?") + +// resourceForURL returns a resource object for the given URL. +func (srv *SwiftServer) resourceForURL(u *url.URL) (r resource) { + m := pathRegexp.FindStringSubmatch(u.Path) + if m == nil { + fatalf(404, "InvalidURI", "Couldn't parse the specified URI") + } + containerName := m[2] + objectName := m[4] + if containerName == "" { + return rootResource{} + } + b := containerResource{ + name: containerName, + container: srv.containers[containerName], + } + + if objectName == "" { + return b + } + + if b.container == nil { + fatalf(404, "NoSuchContainer", "The specified container does not exist") + } + + objr := objectResource{ + name: objectName, + version: u.Query().Get("versionId"), + container: b.container, + } + + if obj := objr.container.objects[objr.name]; obj != nil { + objr.object = obj + } + return objr +} + +// nullResource has error stubs for all resource methods. +type nullResource struct{} + +func notAllowed() interface{} { + fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource") + return nil +} + +func notAuthorized() interface{} { + fatalf(401, "Unauthorized", "This server could not verify that you are authorized to access the document you requested.") + return nil +} + +func (nullResource) put(a *action) interface{} { return notAllowed() } +func (nullResource) get(a *action) interface{} { return notAllowed() } +func (nullResource) post(a *action) interface{} { return notAllowed() } +func (nullResource) delete(a *action) interface{} { return notAllowed() } +func (nullResource) copy(a *action) interface{} { return notAllowed() } + +type rootResource struct{} + +func (rootResource) put(a *action) interface{} { return notAllowed() } +func (rootResource) get(a *action) interface{} { + marker := a.req.Form.Get("marker") + prefix := a.req.Form.Get("prefix") + format := a.req.URL.Query().Get("format") + + h := a.w.Header() + + h.Set("X-Account-Bytes-Used", strconv.Itoa(int(a.user.BytesUsed))) + h.Set("X-Account-Container-Count", strconv.Itoa(int(a.user.Containers))) + h.Set("X-Account-Object-Count", strconv.Itoa(int(a.user.Objects))) + + // add metadata + a.user.metadata.getMetadata(a) + + if a.req.Method == "HEAD" { + return nil + } + + var tmp orderedContainers + // first get all matching objects and arrange them in alphabetical order. + for _, container := range a.srv.containers { + if strings.HasPrefix(container.name, prefix) { + tmp = append(tmp, container) + } + } + sort.Sort(tmp) + + resp := make([]Folder, 0) + for _, container := range tmp { + if container.name <= marker { + continue + } + if format == "json" { + resp = append(resp, Folder{ + Count: len(container.objects), + Bytes: container.bytes, + Name: container.name, + }) + } else { + a.w.Write([]byte(container.name + "\n")) + } + } + + if format == "json" { + return resp + } else { + return nil + } +} + +func (r rootResource) post(a *action) interface{} { + a.user.metadata.setMetadata(a, "account") + return nil +} + +func (rootResource) delete(a *action) interface{} { + if a.req.URL.Query().Get("bulk-delete") == "1" { + fatalf(403, "Operation forbidden", "Bulk delete is not supported") + } + + return notAllowed() +} + +func (rootResource) copy(a *action) interface{} { return notAllowed() } + +func NewSwiftServer(address string) (*SwiftServer, error) { + l, err := net.Listen("tcp", address) + if err != nil { + return nil, fmt.Errorf("cannot listen on %s: %v", address, err) + } + + server := &SwiftServer{ + listener: l, + url: "http://" + l.Addr().String(), + containers: make(map[string]*container), + accounts: make(map[string]*account), + sessions: make(map[string]*session), + } + + server.accounts["swifttest"] = &account{ + password: "swifttest", + metadata: metadata{ + meta: make(http.Header), + }, + } + + go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + server.serveHTTP(w, req) + })) + + return server, nil +} + +func (srv SwiftServer) Close() { + srv.listener.Close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader.go new file mode 100644 index 00000000..3839e9ea --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader.go @@ -0,0 +1,57 @@ +package swift + +import ( + "io" + "time" +) + +// An io.ReadCloser which obeys an idle timeout +type timeoutReader struct { + reader io.ReadCloser + timeout time.Duration + cancel func() +} + +// Returns a wrapper around the reader which obeys an idle +// timeout. The cancel function is called if the timeout happens +func newTimeoutReader(reader io.ReadCloser, timeout time.Duration, cancel func()) *timeoutReader { + return &timeoutReader{ + reader: reader, + timeout: timeout, + cancel: cancel, + } +} + +// Read reads up to len(p) bytes into p +// +// Waits at most for timeout for the read to complete otherwise returns a timeout +func (t *timeoutReader) Read(p []byte) (int, error) { + // FIXME limit the amount of data read in one chunk so as to not exceed the timeout? + // Do the read in the background + type result struct { + n int + err error + } + done := make(chan result, 1) + go func() { + n, err := t.reader.Read(p) + done <- result{n, err} + }() + // Wait for the read or the timeout + select { + case r := <-done: + return r.n, r.err + case <-time.After(t.timeout): + t.cancel() + return 0, TimeoutError + } + panic("unreachable") // for Go 1.0 +} + +// Close the channel +func (t *timeoutReader) Close() error { + return t.reader.Close() +} + +// Check it satisfies the interface +var _ io.ReadCloser = &timeoutReader{} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader_test.go new file mode 100644 index 00000000..2348617b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/timeout_reader_test.go @@ -0,0 +1,107 @@ +// This tests TimeoutReader + +package swift + +import ( + "io" + "io/ioutil" + "sync" + "testing" + "time" +) + +// An io.ReadCloser for testing +type testReader struct { + sync.Mutex + n int + delay time.Duration + closed bool +} + +// Returns n bytes with at time.Duration delay +func newTestReader(n int, delay time.Duration) *testReader { + return &testReader{ + n: n, + delay: delay, + } +} + +// Returns 1 byte at a time after delay +func (t *testReader) Read(p []byte) (n int, err error) { + if t.n <= 0 { + return 0, io.EOF + } + time.Sleep(t.delay) + p[0] = 'A' + t.Lock() + t.n-- + t.Unlock() + return 1, nil +} + +// Close the channel +func (t *testReader) Close() error { + t.Lock() + t.closed = true + t.Unlock() + return nil +} + +func TestTimeoutReaderNoTimeout(t *testing.T) { + test := newTestReader(3, 10*time.Millisecond) + cancelled := false + cancel := func() { + cancelled = true + } + tr := newTimeoutReader(test, 100*time.Millisecond, cancel) + b, err := ioutil.ReadAll(tr) + if err != nil || string(b) != "AAA" { + t.Fatalf("Bad read %s %s", err, b) + } + if cancelled { + t.Fatal("Cancelled when shouldn't have been") + } + if test.n != 0 { + t.Fatal("Didn't read all") + } + if test.closed { + t.Fatal("Shouldn't be closed") + } + tr.Close() + if !test.closed { + t.Fatal("Should be closed") + } +} + +func TestTimeoutReaderTimeout(t *testing.T) { + // Return those bytes slowly so we get an idle timeout + test := newTestReader(3, 100*time.Millisecond) + cancelled := false + cancel := func() { + cancelled = true + } + tr := newTimeoutReader(test, 10*time.Millisecond, cancel) + _, err := ioutil.ReadAll(tr) + if err != TimeoutError { + t.Fatal("Expecting TimeoutError, got", err) + } + if !cancelled { + t.Fatal("Not cancelled when should have been") + } + test.Lock() + n := test.n + test.Unlock() + if n == 0 { + t.Fatal("Read all") + } + if n != 3 { + t.Fatal("Didn't read any") + } + if test.closed { + t.Fatal("Shouldn't be closed") + } + tr.Close() + if !test.closed { + t.Fatal("Should be closed") + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader.go new file mode 100644 index 00000000..b12b1bbe --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader.go @@ -0,0 +1,34 @@ +package swift + +import ( + "io" + "time" +) + +// An io.Reader which resets a watchdog timer whenever data is read +type watchdogReader struct { + timeout time.Duration + reader io.Reader + timer *time.Timer +} + +// Returns a new reader which will kick the watchdog timer whenever data is read +func newWatchdogReader(reader io.Reader, timeout time.Duration, timer *time.Timer) *watchdogReader { + return &watchdogReader{ + timeout: timeout, + reader: reader, + timer: timer, + } +} + +// Read reads up to len(p) bytes into p +func (t *watchdogReader) Read(p []byte) (n int, err error) { + // FIXME limit the amount of data read in one chunk so as to not exceed the timeout? + resetTimer(t.timer, t.timeout) + n, err = t.reader.Read(p) + resetTimer(t.timer, t.timeout) + return +} + +// Check it satisfies the interface +var _ io.Reader = &watchdogReader{} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader_test.go new file mode 100644 index 00000000..8b879d44 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/ncw/swift/watchdog_reader_test.go @@ -0,0 +1,61 @@ +// This tests WatchdogReader + +package swift + +import ( + "io/ioutil" + "testing" + "time" +) + +// Uses testReader from timeout_reader_test.go + +func testWatchdogReaderTimeout(t *testing.T, initialTimeout, watchdogTimeout time.Duration, expectedTimeout bool) { + test := newTestReader(3, 10*time.Millisecond) + timer := time.NewTimer(initialTimeout) + firedChan := make(chan bool) + started := make(chan bool) + go func() { + started <- true + select { + case <-timer.C: + firedChan <- true + } + }() + <-started + wr := newWatchdogReader(test, watchdogTimeout, timer) + b, err := ioutil.ReadAll(wr) + if err != nil || string(b) != "AAA" { + t.Fatalf("Bad read %s %s", err, b) + } + fired := false + select { + case fired = <-firedChan: + default: + } + if expectedTimeout { + if !fired { + t.Fatal("Timer should have fired") + } + } else { + if fired { + t.Fatal("Timer should not have fired") + } + } +} + +func TestWatchdogReaderNoTimeout(t *testing.T) { + testWatchdogReaderTimeout(t, 100*time.Millisecond, 100*time.Millisecond, false) +} + +func TestWatchdogReaderTimeout(t *testing.T) { + testWatchdogReaderTimeout(t, 5*time.Millisecond, 5*time.Millisecond, true) +} + +func TestWatchdogReaderNoTimeoutShortInitial(t *testing.T) { + testWatchdogReaderTimeout(t, 5*time.Millisecond, 100*time.Millisecond, false) +} + +func TestWatchdogReaderTimeoutLongInitial(t *testing.T) { + testWatchdogReaderTimeout(t, 100*time.Millisecond, 5*time.Millisecond, true) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch.go index 0d7911ec..ccf390c9 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch.go @@ -8,65 +8,84 @@ package leveldb import ( "encoding/binary" - "errors" + "fmt" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/memdb" ) -var ( - errBatchTooShort = errors.New("leveldb: batch is too short") - errBatchBadRecord = errors.New("leveldb: bad record in batch") +type ErrBatchCorrupted struct { + Reason string +} + +func (e *ErrBatchCorrupted) Error() string { + return fmt.Sprintf("leveldb: batch corrupted: %s", e.Reason) +} + +func newErrBatchCorrupted(reason string) error { + return errors.NewErrCorrupted(nil, &ErrBatchCorrupted{reason}) +} + +const ( + batchHdrLen = 8 + 4 + batchGrowRec = 3000 ) -const kBatchHdrLen = 8 + 4 - -type batchReplay interface { - put(key, value []byte, seq uint64) - delete(key []byte, seq uint64) +type BatchReplay interface { + Put(key, value []byte) + Delete(key []byte) } // Batch is a write batch. type Batch struct { - buf []byte + data []byte rLen, bLen int seq uint64 sync bool } func (b *Batch) grow(n int) { - off := len(b.buf) + off := len(b.data) if off == 0 { - // include headers - off = kBatchHdrLen - n += off + off = batchHdrLen + if b.data != nil { + b.data = b.data[:off] + } } - if cap(b.buf)-off >= n { - return + if cap(b.data)-off < n { + if b.data == nil { + b.data = make([]byte, off, off+n) + } else { + odata := b.data + div := 1 + if b.rLen > batchGrowRec { + div = b.rLen / batchGrowRec + } + b.data = make([]byte, off, off+n+(off-batchHdrLen)/div) + copy(b.data, odata) + } } - buf := make([]byte, 2*cap(b.buf)+n) - copy(buf, b.buf) - b.buf = buf[:off] } -func (b *Batch) appendRec(t vType, key, value []byte) { +func (b *Batch) appendRec(kt kType, key, value []byte) { n := 1 + binary.MaxVarintLen32 + len(key) - if t == tVal { + if kt == ktVal { n += binary.MaxVarintLen32 + len(value) } b.grow(n) - off := len(b.buf) - buf := b.buf[:off+n] - buf[off] = byte(t) + off := len(b.data) + data := b.data[:off+n] + data[off] = byte(kt) off += 1 - off += binary.PutUvarint(buf[off:], uint64(len(key))) - copy(buf[off:], key) + off += binary.PutUvarint(data[off:], uint64(len(key))) + copy(data[off:], key) off += len(key) - if t == tVal { - off += binary.PutUvarint(buf[off:], uint64(len(value))) - copy(buf[off:], value) + if kt == ktVal { + off += binary.PutUvarint(data[off:], uint64(len(value))) + copy(data[off:], value) off += len(value) } - b.buf = buf[:off] + b.data = data[:off] b.rLen++ // Include 8-byte ikey header b.bLen += len(key) + len(value) + 8 @@ -75,18 +94,51 @@ func (b *Batch) appendRec(t vType, key, value []byte) { // Put appends 'put operation' of the given key/value pair to the batch. // It is safe to modify the contents of the argument after Put returns. func (b *Batch) Put(key, value []byte) { - b.appendRec(tVal, key, value) + b.appendRec(ktVal, key, value) } // Delete appends 'delete operation' of the given key to the batch. // It is safe to modify the contents of the argument after Delete returns. func (b *Batch) Delete(key []byte) { - b.appendRec(tDel, key, nil) + b.appendRec(ktDel, key, nil) +} + +// Dump dumps batch contents. The returned slice can be loaded into the +// batch using Load method. +// The returned slice is not its own copy, so the contents should not be +// modified. +func (b *Batch) Dump() []byte { + return b.encode() +} + +// Load loads given slice into the batch. Previous contents of the batch +// will be discarded. +// The given slice will not be copied and will be used as batch buffer, so +// it is not safe to modify the contents of the slice. +func (b *Batch) Load(data []byte) error { + return b.decode(0, data) +} + +// Replay replays batch contents. +func (b *Batch) Replay(r BatchReplay) error { + return b.decodeRec(func(i int, kt kType, key, value []byte) { + switch kt { + case ktVal: + r.Put(key, value) + case ktDel: + r.Delete(key) + } + }) +} + +// Len returns number of records in the batch. +func (b *Batch) Len() int { + return b.rLen } // Reset resets the batch. func (b *Batch) Reset() { - b.buf = nil + b.data = b.data[:0] b.seq = 0 b.rLen = 0 b.bLen = 0 @@ -97,24 +149,10 @@ func (b *Batch) init(sync bool) { b.sync = sync } -func (b *Batch) put(key, value []byte, seq uint64) { - if b.rLen == 0 { - b.seq = seq - } - b.Put(key, value) -} - -func (b *Batch) delete(key []byte, seq uint64) { - if b.rLen == 0 { - b.seq = seq - } - b.Delete(key) -} - func (b *Batch) append(p *Batch) { if p.rLen > 0 { - b.grow(len(p.buf) - kBatchHdrLen) - b.buf = append(b.buf, p.buf[kBatchHdrLen:]...) + b.grow(len(p.data) - batchHdrLen) + b.data = append(b.data, p.data[batchHdrLen:]...) b.rLen += p.rLen } if p.sync { @@ -122,95 +160,93 @@ func (b *Batch) append(p *Batch) { } } -func (b *Batch) len() int { - return b.rLen -} - +// size returns sums of key/value pair length plus 8-bytes ikey. func (b *Batch) size() int { return b.bLen } func (b *Batch) encode() []byte { b.grow(0) - binary.LittleEndian.PutUint64(b.buf, b.seq) - binary.LittleEndian.PutUint32(b.buf[8:], uint32(b.rLen)) + binary.LittleEndian.PutUint64(b.data, b.seq) + binary.LittleEndian.PutUint32(b.data[8:], uint32(b.rLen)) - return b.buf + return b.data } -func (b *Batch) decode(buf []byte) error { - if len(buf) < kBatchHdrLen { - return errBatchTooShort +func (b *Batch) decode(prevSeq uint64, data []byte) error { + if len(data) < batchHdrLen { + return newErrBatchCorrupted("too short") } - b.seq = binary.LittleEndian.Uint64(buf) - b.rLen = int(binary.LittleEndian.Uint32(buf[8:])) + b.seq = binary.LittleEndian.Uint64(data) + if b.seq < prevSeq { + return newErrBatchCorrupted("invalid sequence number") + } + b.rLen = int(binary.LittleEndian.Uint32(data[8:])) + if b.rLen < 0 { + return newErrBatchCorrupted("invalid records length") + } // No need to be precise at this point, it won't be used anyway - b.bLen = len(buf) - kBatchHdrLen - b.buf = buf + b.bLen = len(data) - batchHdrLen + b.data = data return nil } -func (b *Batch) decodeRec(f func(i int, t vType, key, value []byte)) error { - off := kBatchHdrLen +func (b *Batch) decodeRec(f func(i int, kt kType, key, value []byte)) (err error) { + off := batchHdrLen for i := 0; i < b.rLen; i++ { - if off >= len(b.buf) { - return errors.New("leveldb: invalid batch record length") + if off >= len(b.data) { + return newErrBatchCorrupted("invalid records length") } - t := vType(b.buf[off]) - if t > tVal { - return errors.New("leveldb: invalid batch record type in batch") + kt := kType(b.data[off]) + if kt > ktVal { + return newErrBatchCorrupted("bad record: invalid type") } off += 1 - x, n := binary.Uvarint(b.buf[off:]) + x, n := binary.Uvarint(b.data[off:]) off += n - if n <= 0 || off+int(x) > len(b.buf) { - return errBatchBadRecord + if n <= 0 || off+int(x) > len(b.data) { + return newErrBatchCorrupted("bad record: invalid key length") } - key := b.buf[off : off+int(x)] + key := b.data[off : off+int(x)] off += int(x) - var value []byte - if t == tVal { - x, n := binary.Uvarint(b.buf[off:]) + if kt == ktVal { + x, n := binary.Uvarint(b.data[off:]) off += n - if n <= 0 || off+int(x) > len(b.buf) { - return errBatchBadRecord + if n <= 0 || off+int(x) > len(b.data) { + return newErrBatchCorrupted("bad record: invalid value length") } - value = b.buf[off : off+int(x)] + value = b.data[off : off+int(x)] off += int(x) } - f(i, t, key, value) + f(i, kt, key, value) } return nil } -func (b *Batch) replay(to batchReplay) error { - return b.decodeRec(func(i int, t vType, key, value []byte) { - switch t { - case tVal: - to.put(key, value, b.seq+uint64(i)) - case tDel: - to.delete(key, b.seq+uint64(i)) - } - }) -} - func (b *Batch) memReplay(to *memdb.DB) error { - return b.decodeRec(func(i int, t vType, key, value []byte) { - ikey := newIKey(key, b.seq+uint64(i), t) + return b.decodeRec(func(i int, kt kType, key, value []byte) { + ikey := newIkey(key, b.seq+uint64(i), kt) to.Put(ikey, value) }) } +func (b *Batch) memDecodeAndReplay(prevSeq uint64, data []byte, to *memdb.DB) error { + if err := b.decode(prevSeq, data); err != nil { + return err + } + return b.memReplay(to) +} + func (b *Batch) revertMemReplay(to *memdb.DB) error { - return b.decodeRec(func(i int, t vType, key, value []byte) { - ikey := newIKey(key, b.seq+uint64(i), t) + return b.decodeRec(func(i int, kt kType, key, value []byte) { + ikey := newIkey(key, b.seq+uint64(i), kt) to.Delete(ikey) }) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch_test.go index 19b749b8..7fc842f4 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/batch_test.go @@ -15,7 +15,7 @@ import ( ) type tbRec struct { - t vType + kt kType key, value []byte } @@ -23,39 +23,39 @@ type testBatch struct { rec []*tbRec } -func (p *testBatch) put(key, value []byte, seq uint64) { - p.rec = append(p.rec, &tbRec{tVal, key, value}) +func (p *testBatch) Put(key, value []byte) { + p.rec = append(p.rec, &tbRec{ktVal, key, value}) } -func (p *testBatch) delete(key []byte, seq uint64) { - p.rec = append(p.rec, &tbRec{tDel, key, nil}) +func (p *testBatch) Delete(key []byte) { + p.rec = append(p.rec, &tbRec{ktDel, key, nil}) } func compareBatch(t *testing.T, b1, b2 *Batch) { if b1.seq != b2.seq { t.Errorf("invalid seq number want %d, got %d", b1.seq, b2.seq) } - if b1.len() != b2.len() { - t.Fatalf("invalid record length want %d, got %d", b1.len(), b2.len()) + if b1.Len() != b2.Len() { + t.Fatalf("invalid record length want %d, got %d", b1.Len(), b2.Len()) } p1, p2 := new(testBatch), new(testBatch) - err := b1.replay(p1) + err := b1.Replay(p1) if err != nil { t.Fatal("error when replaying batch 1: ", err) } - err = b2.replay(p2) + err = b2.Replay(p2) if err != nil { t.Fatal("error when replaying batch 2: ", err) } for i := range p1.rec { r1, r2 := p1.rec[i], p2.rec[i] - if r1.t != r2.t { - t.Errorf("invalid type on record '%d' want %d, got %d", i, r1.t, r2.t) + if r1.kt != r2.kt { + t.Errorf("invalid type on record '%d' want %d, got %d", i, r1.kt, r2.kt) } if !bytes.Equal(r1.key, r2.key) { t.Errorf("invalid key on record '%d' want %s, got %s", i, string(r1.key), string(r2.key)) } - if r1.t == tVal { + if r1.kt == ktVal { if !bytes.Equal(r1.value, r2.value) { t.Errorf("invalid value on record '%d' want %s, got %s", i, string(r1.value), string(r2.value)) } @@ -75,7 +75,7 @@ func TestBatch_EncodeDecode(t *testing.T) { b1.Delete([]byte("k")) buf := b1.encode() b2 := new(Batch) - err := b2.decode(buf) + err := b2.decode(0, buf) if err != nil { t.Error("error when decoding batch: ", err) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go index 6207e681..865bc573 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go @@ -249,7 +249,7 @@ func (x *testingCacheObject) Release() { x.releaseCalled = true x.cnt.releaseOne() } else { - x.t.Errorf("duplicate setfin NS#%d KEY#%s", x.ns, x.key) + x.t.Errorf("duplicate setfin NS#%d KEY#%d", x.ns, x.key) } } @@ -489,7 +489,7 @@ func TestLRUCache_Finalizer(t *testing.T) { return true } else { if p.delfinCalled != keymax { - t.Errorf("(2) #%d not all delete fin called, diff=%d", p.ns, keymax-p.delfinCalled) + t.Errorf("(2) NS#%d not all delete fin called, diff=%d", p.nsid, keymax-p.delfinCalled) } return false } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/config.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/config.go deleted file mode 100644 index 51105889..00000000 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/config.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2012, Suryandaru Triandana -// All rights reserved. -// -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package leveldb - -const ( - kNumLevels = 7 - - // Level-0 compaction is started when we hit this many files. - kL0_CompactionTrigger float64 = 4 - - // Soft limit on number of level-0 files. We slow down writes at this point. - kL0_SlowdownWritesTrigger = 8 - - // Maximum number of level-0 files. We stop writes at this point. - kL0_StopWritesTrigger = 12 - - // Maximum level to which a new compacted memdb is pushed if it - // does not create overlap. We try to push to level 2 to avoid the - // relatively expensive level 0=>1 compactions and to avoid some - // expensive manifest file operations. We do not push all the way to - // the largest level since that can generate a lot of wasted disk - // space if the same key space is being repeatedly overwritten. - kMaxMemCompactLevel = 2 - - // Maximum size of a table. - kMaxTableSize = 2 * 1048576 - - // Maximum bytes of overlaps in grandparent (i.e., level+2) before we - // stop building a single file in a level->level+1 compaction. - kMaxGrandParentOverlapBytes = 10 * kMaxTableSize - - // Maximum number of bytes in all compacted files. We avoid expanding - // the lower level file set of a compaction if it would make the - // total compaction cover more than this many bytes. - kExpCompactionMaxBytes = 25 * kMaxTableSize -) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go index a036e089..427d7221 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/syndtr/goleveldb/leveldb/cache" + "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/storage" ) @@ -96,21 +97,22 @@ func (h *dbCorruptHarness) deleteRand(n, max int, rnd *rand.Rand) { } } -func (h *dbCorruptHarness) corrupt(ft storage.FileType, offset, n int) { +func (h *dbCorruptHarness) corrupt(ft storage.FileType, fi, offset, n int) { p := &h.dbHarness t := p.t - var file storage.File ff, _ := p.stor.GetFiles(ft) - for _, f := range ff { - if file == nil || f.Num() > file.Num() { - file = f - } + sff := files(ff) + sff.sort() + if fi < 0 { + fi = len(sff) - 1 } - if file == nil { - t.Fatalf("no such file with type %q", ft) + if fi >= len(sff) { + t.Fatalf("no such file with type %q with index %d", ft, fi) } + file := sff[fi] + r, err := file.Open() if err != nil { t.Fatal("cannot open file: ", err) @@ -225,8 +227,8 @@ func TestCorruptDB_Journal(t *testing.T) { h.build(100) h.check(100, 100) h.closeDB() - h.corrupt(storage.TypeJournal, 19, 1) - h.corrupt(storage.TypeJournal, 32*1024+1000, 1) + h.corrupt(storage.TypeJournal, -1, 19, 1) + h.corrupt(storage.TypeJournal, -1, 32*1024+1000, 1) h.openDB() h.check(36, 36) @@ -242,7 +244,7 @@ func TestCorruptDB_Table(t *testing.T) { h.compactRangeAt(0, "", "") h.compactRangeAt(1, "", "") h.closeDB() - h.corrupt(storage.TypeTable, 100, 1) + h.corrupt(storage.TypeTable, -1, 100, 1) h.openDB() h.check(99, 99) @@ -256,7 +258,7 @@ func TestCorruptDB_TableIndex(t *testing.T) { h.build(10000) h.compactMem() h.closeDB() - h.corrupt(storage.TypeTable, -2000, 500) + h.corrupt(storage.TypeTable, -1, -2000, 500) h.openDB() h.check(5000, 9999) @@ -355,7 +357,7 @@ func TestCorruptDB_CorruptedManifest(t *testing.T) { h.compactMem() h.compactRange("", "") h.closeDB() - h.corrupt(storage.TypeManifest, 0, 1000) + h.corrupt(storage.TypeManifest, -1, 0, 1000) h.openAssert(false) h.recover() @@ -370,7 +372,7 @@ func TestCorruptDB_CompactionInputError(t *testing.T) { h.build(10) h.compactMem() h.closeDB() - h.corrupt(storage.TypeTable, 100, 1) + h.corrupt(storage.TypeTable, -1, 100, 1) h.openDB() h.check(9, 9) @@ -387,7 +389,7 @@ func TestCorruptDB_UnrelatedKeys(t *testing.T) { h.build(10) h.compactMem() h.closeDB() - h.corrupt(storage.TypeTable, 100, 1) + h.corrupt(storage.TypeTable, -1, 100, 1) h.openDB() h.put(string(tkey(1000)), string(tval(1000, ctValSize))) @@ -470,3 +472,31 @@ func TestCorruptDB_MissingTableFiles(t *testing.T) { h.close() } + +func TestCorruptDB_RecoverTable(t *testing.T) { + h := newDbCorruptHarnessWopt(t, &opt.Options{ + WriteBuffer: 112 * opt.KiB, + CompactionTableSize: 90 * opt.KiB, + Filter: filter.NewBloomFilter(10), + }) + + h.build(1000) + h.compactMem() + h.compactRangeAt(0, "", "") + h.compactRangeAt(1, "", "") + seq := h.db.seq + h.closeDB() + h.corrupt(storage.TypeTable, 0, 1000, 1) + h.corrupt(storage.TypeTable, 3, 10000, 1) + // Corrupted filter shouldn't affect recovery. + h.corrupt(storage.TypeTable, 3, 113888, 10) + h.corrupt(storage.TypeTable, -1, 20000, 1) + + h.recover() + if h.db.seq != seq { + t.Errorf("invalid seq, want=%d got=%d", seq, h.db.seq) + } + h.check(985, 985) + + h.close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go index 979d0ac4..70c847f8 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db.go @@ -7,7 +7,7 @@ package leveldb import ( - "errors" + "container/list" "fmt" "io" "os" @@ -17,6 +17,7 @@ import ( "sync/atomic" "time" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/journal" "github.com/syndtr/goleveldb/leveldb/memdb" @@ -46,7 +47,7 @@ type DB struct { // Snapshot. snapsMu sync.Mutex - snapsRoot snapshotElement + snapsList *list.List // Stats. aliveSnaps, aliveIters int32 @@ -56,18 +57,19 @@ type DB struct { writeMergedC chan bool writeLockC chan struct{} writeAckC chan error + writeDelay time.Duration + writeDelayN int journalC chan *Batch journalAckC chan error // Compaction. - tcompCmdC chan cCmd - tcompPauseC chan chan<- struct{} - tcompTriggerC chan struct{} - mcompCmdC chan cCmd - mcompTriggerC chan struct{} - compErrC chan error - compErrSetC chan error - compStats [kNumLevels]cStats + tcompCmdC chan cCmd + tcompPauseC chan chan<- struct{} + mcompCmdC chan cCmd + compErrC chan error + compPerErrC chan error + compErrSetC chan error + compStats []cStats // Close. closeW sync.WaitGroup @@ -82,9 +84,11 @@ func openDB(s *session) (*DB, error) { db := &DB{ s: s, // Initial sequence - seq: s.stSeq, + seq: s.stSeqNum, // MemDB memPool: make(chan *memdb.DB, 1), + // Snapshot + snapsList: list.New(), // Write writeC: make(chan *Batch), writeMergedC: make(chan bool), @@ -93,17 +97,16 @@ func openDB(s *session) (*DB, error) { journalC: make(chan *Batch), journalAckC: make(chan error), // Compaction - tcompCmdC: make(chan cCmd), - tcompPauseC: make(chan chan<- struct{}), - tcompTriggerC: make(chan struct{}, 1), - mcompCmdC: make(chan cCmd), - mcompTriggerC: make(chan struct{}, 1), - compErrC: make(chan error), - compErrSetC: make(chan error), + tcompCmdC: make(chan cCmd), + tcompPauseC: make(chan chan<- struct{}), + mcompCmdC: make(chan cCmd), + compErrC: make(chan error), + compPerErrC: make(chan error), + compErrSetC: make(chan error), + compStats: make([]cStats, s.o.GetNumLevel()), // Close closeC: make(chan struct{}), } - db.initSnapshot() if err := db.recoverJournal(); err != nil { return nil, err @@ -119,14 +122,14 @@ func openDB(s *session) (*DB, error) { return nil, err } - // Don't include compaction error goroutine into wait group. + // Doesn't need to be included in the wait group. go db.compactionError() + go db.mpoolDrain() db.closeW.Add(3) go db.tCompaction() go db.mCompaction() go db.jWriter() - go db.mpoolDrain() s.logf("db@open done T·%v", time.Since(start)) @@ -253,6 +256,10 @@ func RecoverFile(path string, o *opt.Options) (db *DB, err error) { } func recoverTable(s *session, o *opt.Options) error { + o = dupOptions(o) + // Mask StrictReader, lets StrictRecovery doing its job. + o.Strict &= ^opt.StrictReader + // Get all tables and sort it by file number. tableFiles_, err := s.getFiles(storage.TypeTable) if err != nil { @@ -261,10 +268,16 @@ func recoverTable(s *session, o *opt.Options) error { tableFiles := files(tableFiles_) tableFiles.sort() - var mSeq uint64 - var good, corrupted int - rec := new(sessionRecord) - bpool := util.NewBufferPool(o.GetBlockSize() + 5) + var ( + maxSeq uint64 + recoveredKey, goodKey, corruptedKey, corruptedBlock, droppedTable int + + // We will drop corrupted table. + strict = o.GetStrict(opt.StrictRecovery) + + rec = &sessionRecord{numLevel: o.GetNumLevel()} + bpool = util.NewBufferPool(o.GetBlockSize() + 5) + ) buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) { tmp = s.newTemp() writer, err := tmp.Create() @@ -311,7 +324,12 @@ func recoverTable(s *session, o *opt.Options) error { if err != nil { return err } - defer reader.Close() + var closed bool + defer func() { + if !closed { + reader.Close() + } + }() // Get file size. size, err := reader.Seek(0, 2) @@ -319,25 +337,32 @@ func recoverTable(s *session, o *opt.Options) error { return err } - var tSeq uint64 - var tgood, tcorrupted, blockerr int - var imin, imax []byte - tr := table.NewReader(reader, size, nil, bpool, o) + var ( + tSeq uint64 + tgoodKey, tcorruptedKey, tcorruptedBlock int + imin, imax []byte + ) + tr, err := table.NewReader(reader, size, storage.NewFileInfo(file), nil, bpool, o) + if err != nil { + return err + } iter := tr.NewIterator(nil, nil) iter.(iterator.ErrorCallbackSetter).SetErrorCallback(func(err error) { - s.logf("table@recovery found error @%d %q", file.Num(), err) - blockerr++ + if errors.IsCorrupted(err) { + s.logf("table@recovery block corruption @%d %q", file.Num(), err) + tcorruptedBlock++ + } }) // Scan the table. for iter.Next() { key := iter.Key() - _, seq, _, ok := parseIkey(key) - if !ok { - tcorrupted++ + _, seq, _, kerr := parseIkey(key) + if kerr != nil { + tcorruptedKey++ continue } - tgood++ + tgoodKey++ if seq > tSeq { tSeq = seq } @@ -352,8 +377,18 @@ func recoverTable(s *session, o *opt.Options) error { } iter.Release() - if tgood > 0 { - if tcorrupted > 0 || blockerr > 0 { + goodKey += tgoodKey + corruptedKey += tcorruptedKey + corruptedBlock += tcorruptedBlock + + if strict && (tcorruptedKey > 0 || tcorruptedBlock > 0) { + droppedTable++ + s.logf("table@recovery dropped @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", file.Num(), tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq) + return nil + } + + if tgoodKey > 0 { + if tcorruptedKey > 0 || tcorruptedBlock > 0 { // Rebuild the table. s.logf("table@recovery rebuilding @%d", file.Num()) iter := tr.NewIterator(nil, nil) @@ -362,25 +397,25 @@ func recoverTable(s *session, o *opt.Options) error { if err != nil { return err } + closed = true reader.Close() if err := file.Replace(tmp); err != nil { return err } size = newSize } - if tSeq > mSeq { - mSeq = tSeq + if tSeq > maxSeq { + maxSeq = tSeq } + recoveredKey += tgoodKey // Add table to level 0. rec.addTable(0, file.Num(), uint64(size), imin, imax) - s.logf("table@recovery recovered @%d N·%d C·%d B·%d S·%d Q·%d", file.Num(), tgood, tcorrupted, blockerr, size, tSeq) + s.logf("table@recovery recovered @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", file.Num(), tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq) } else { - s.logf("table@recovery unrecoverable @%d C·%d B·%d S·%d", file.Num(), tcorrupted, blockerr, size) + droppedTable++ + s.logf("table@recovery unrecoverable @%d Ck·%d Cb·%d S·%d", file.Num(), tcorruptedKey, tcorruptedBlock, size) } - good += tgood - corrupted += tcorrupted - return nil } @@ -397,11 +432,11 @@ func recoverTable(s *session, o *opt.Options) error { } } - s.logf("table@recovery recovered F·%d N·%d C·%d Q·%d", len(tableFiles), good, corrupted, mSeq) + s.logf("table@recovery recovered F·%d N·%d Gk·%d Ck·%d Q·%d", len(tableFiles), recoveredKey, goodKey, corruptedKey, maxSeq) } // Set sequence number. - rec.setSeq(mSeq + 1) + rec.setSeqNum(maxSeq) // Create new manifest. if err := s.create(); err != nil { @@ -484,26 +519,30 @@ func (db *DB) recoverJournal() error { if err == io.EOF { break } - return err + return errors.SetFile(err, file) } buf.Reset() if _, err := buf.ReadFrom(r); err != nil { if err == io.ErrUnexpectedEOF { + // This is error returned due to corruption, with strict == false. continue } else { - return err + return errors.SetFile(err, file) } } - if err := batch.decode(buf.Bytes()); err != nil { - return err - } - if err := batch.memReplay(mem); err != nil { - return err + if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mem); err != nil { + if strict || !errors.IsCorrupted(err) { + return errors.SetFile(err, file) + } else { + db.s.logf("journal error: %v (skipped)", err) + // We won't apply sequence number as it might be corrupted. + continue + } } // Save sequence number. - db.seq = batch.seq + uint64(batch.len()) + db.seq = batch.seq + uint64(batch.Len()) // Flush it if large enough. if mem.Size() >= writeBuffer { @@ -564,7 +603,7 @@ func (db *DB) recoverJournal() error { } func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) { - ikey := newIKey(key, seq, tSeek) + ikey := newIkey(key, seq, ktSeek) em, fm := db.getMems() for _, m := range [...]*memDB{em, fm} { @@ -575,9 +614,13 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er mk, mv, me := m.mdb.Find(ikey) if me == nil { - ukey, _, t, ok := parseIkey(mk) - if ok && db.s.icmp.uCompare(ukey, key) == 0 { - if t == tDel { + ukey, _, kt, kerr := parseIkey(mk) + if kerr != nil { + // Shouldn't have had happen. + panic(kerr) + } + if db.s.icmp.uCompare(ukey, key) == 0 { + if kt == ktDel { return nil, ErrNotFound } return append([]byte{}, mv...), nil @@ -588,17 +631,60 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er } v := db.s.version() - value, cSched, err := v.get(ikey, ro) + value, cSched, err := v.get(ikey, ro, false) v.release() if cSched { // Trigger table compaction. - db.compTrigger(db.tcompTriggerC) + db.compSendTrigger(db.tcompCmdC) + } + return +} + +func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err error) { + ikey := newIkey(key, seq, ktSeek) + + em, fm := db.getMems() + for _, m := range [...]*memDB{em, fm} { + if m == nil { + continue + } + defer m.decref() + + mk, _, me := m.mdb.Find(ikey) + if me == nil { + ukey, _, kt, kerr := parseIkey(mk) + if kerr != nil { + // Shouldn't have had happen. + panic(kerr) + } + if db.s.icmp.uCompare(ukey, key) == 0 { + if kt == ktDel { + return false, nil + } + return true, nil + } + } else if me != ErrNotFound { + return false, me + } + } + + v := db.s.version() + _, cSched, err := v.get(ikey, ro, true) + v.release() + if cSched { + // Trigger table compaction. + db.compSendTrigger(db.tcompCmdC) + } + if err == nil { + ret = true + } else if err == ErrNotFound { + err = nil } return } // Get gets the value for the given key. It returns ErrNotFound if the -// DB does not contain the key. +// DB does not contains the key. // // The returned slice is its own copy, it is safe to modify the contents // of the returned slice. @@ -609,7 +695,23 @@ func (db *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { return } - return db.get(key, db.getSeq(), ro) + se := db.acquireSnapshot() + defer db.releaseSnapshot(se) + return db.get(key, se.seq, ro) +} + +// Has returns true if the DB does contains the given key. +// +// It is safe to modify the contents of the argument after Get returns. +func (db *DB) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) { + err = db.ok() + if err != nil { + return + } + + se := db.acquireSnapshot() + defer db.releaseSnapshot(se) + return db.has(key, se.seq, ro) } // NewIterator returns an iterator for the latest snapshot of the @@ -633,9 +735,11 @@ func (db *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Itera return iterator.NewEmptyIterator(err) } - snap := db.newSnapshot() - defer snap.Release() - return snap.NewIterator(slice, ro) + se := db.acquireSnapshot() + defer db.releaseSnapshot(se) + // Iterator holds 'version' lock, 'version' is immutable so snapshot + // can be released after iterator created. + return db.newIterator(se.seq, slice, ro) } // GetSnapshot returns a latest snapshot of the underlying DB. A snapshot @@ -655,7 +759,7 @@ func (db *DB) GetSnapshot() (*Snapshot, error) { // // Property names: // leveldb.num-files-at-level{n} -// Returns the number of filer at level 'n'. +// Returns the number of files at level 'n'. // leveldb.stats // Returns statistics of the underlying DB. // leveldb.sstables @@ -685,12 +789,13 @@ func (db *DB) GetProperty(name string) (value string, err error) { v := db.s.version() defer v.release() + numFilesPrefix := "num-files-at-level" switch { - case strings.HasPrefix(p, "num-files-at-level"): + case strings.HasPrefix(p, numFilesPrefix): var level uint var rest string - n, _ := fmt.Scanf("%d%s", &level, &rest) - if n != 1 || level >= kNumLevels { + n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest) + if n != 1 || int(level) >= db.s.o.GetNumLevel() { err = errors.New("leveldb: GetProperty: invalid property: " + name) } else { value = fmt.Sprint(v.tLen(int(level))) @@ -752,8 +857,8 @@ func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) { sizes := make(Sizes, 0, len(ranges)) for _, r := range ranges { - imin := newIKey(r.Start, kMaxSeq, tSeek) - imax := newIKey(r.Limit, kMaxSeq, tSeek) + imin := newIkey(r.Start, kMaxSeq, ktSeek) + imax := newIkey(r.Limit, kMaxSeq, ktSeek) start, err := v.offsetOf(imin) if err != nil { return nil, err @@ -796,18 +901,23 @@ func (db *DB) Close() error { default: } + // Signal all goroutines. close(db.closeC) - // Wait for the close WaitGroup. + // Wait for all gorotines to exit. db.closeW.Wait() - // Close journal. + // Lock writer and closes journal. db.writeLockC <- struct{}{} if db.journal != nil { db.journal.Close() db.journalWriter.Close() } + if db.writeDelayN > 0 { + db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay) + } + // Close session. db.s.close() db.logf("db@close done T·%v", time.Since(start)) @@ -827,7 +937,6 @@ func (db *DB) Close() error { db.journalWriter = nil db.journalFile = nil db.frozenJournalFile = nil - db.snapsRoot = snapshotElement{} db.closer = nil return err diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go index 4c903208..447407ab 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go @@ -7,11 +7,12 @@ package leveldb import ( - "errors" "sync" "time" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/memdb" + "github.com/syndtr/goleveldb/leveldb/opt" ) var ( @@ -68,7 +69,7 @@ type cMem struct { } func newCMem(s *session) *cMem { - return &cMem{s: s, rec: new(sessionRecord)} + return &cMem{s: s, rec: &sessionRecord{numLevel: s.o.GetNumLevel()}} } func (c *cMem) flush(mem *memdb.DB, level int) error { @@ -84,7 +85,9 @@ func (c *cMem) flush(mem *memdb.DB, level int) error { // Pick level. if level < 0 { - level = s.version_NB().pickLevel(t.imin.ukey(), t.imax.ukey()) + v := s.version() + level = v.pickLevel(t.imin.ukey(), t.imax.ukey()) + v.release() } c.rec.addTableFile(level, t) @@ -95,24 +98,32 @@ func (c *cMem) flush(mem *memdb.DB, level int) error { } func (c *cMem) reset() { - c.rec = new(sessionRecord) + c.rec = &sessionRecord{numLevel: c.s.o.GetNumLevel()} } func (c *cMem) commit(journal, seq uint64) error { c.rec.setJournalNum(journal) - c.rec.setSeq(seq) + c.rec.setSeqNum(seq) // Commit changes. return c.s.commit(c.rec) } func (db *DB) compactionError() { - var err error + var ( + err error + wlocked bool + ) noerr: + // No error. for { select { case err = <-db.compErrSetC: - if err != nil { + switch { + case err == nil: + case errors.IsCorrupted(err): + goto hasperr + default: goto haserr } case _, _ = <-db.closeC: @@ -120,17 +131,39 @@ noerr: } } haserr: + // Transient error. for { select { case db.compErrC <- err: case err = <-db.compErrSetC: - if err == nil { + switch { + case err == nil: goto noerr + case errors.IsCorrupted(err): + goto hasperr + default: } case _, _ = <-db.closeC: return } } +hasperr: + // Persistent error. + for { + select { + case db.compErrC <- err: + case db.compPerErrC <- err: + case db.writeLockC <- struct{}{}: + // Hold write lock, so that write won't pass-through. + wlocked = true + case _, _ = <-db.closeC: + if wlocked { + // We should release the lock or Close will hang. + <-db.writeLockC + } + return + } + } } type compactionTransactCounter int @@ -139,12 +172,17 @@ func (cnt *compactionTransactCounter) incr() { *cnt++ } -func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactCounter) error, rollback func() error) { +type compactionTransactInterface interface { + run(cnt *compactionTransactCounter) error + revert() error +} + +func (db *DB) compactionTransact(name string, t compactionTransactInterface) { defer func() { if x := recover(); x != nil { - if x == errCompactionTransactExiting && rollback != nil { - if err := rollback(); err != nil { - db.logf("%s rollback error %q", name, err) + if x == errCompactionTransactExiting { + if err := t.revert(); err != nil { + db.logf("%s revert error %q", name, err) } } panic(x) @@ -156,9 +194,13 @@ func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactC backoffMax = 8 * time.Second backoffMul = 2 * time.Second ) - backoff := backoffMin - backoffT := time.NewTimer(backoff) - lastCnt := compactionTransactCounter(0) + var ( + backoff = backoffMin + backoffT = time.NewTimer(backoff) + lastCnt = compactionTransactCounter(0) + + disableBackoff = db.s.o.GetDisableCompactionBackoff() + ) for n := 0; ; n++ { // Check wether the DB is closed. if db.isClosed() { @@ -170,11 +212,19 @@ func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactC // Execute. cnt := compactionTransactCounter(0) - err := exec(&cnt) + err := t.run(&cnt) + if err != nil { + db.logf("%s error I·%d %q", name, cnt, err) + } // Set compaction error status. select { case db.compErrSetC <- err: + case perr := <-db.compPerErrC: + if err != nil { + db.logf("%s exiting (persistent error %q)", name, perr) + db.compactionExitTransact() + } case _, _ = <-db.closeC: db.logf("%s exiting", name) db.compactionExitTransact() @@ -182,31 +232,56 @@ func (db *DB) compactionTransact(name string, exec func(cnt *compactionTransactC if err == nil { return } - db.logf("%s error I·%d %q", name, cnt, err) - - // Reset backoff duration if counter is advancing. - if cnt > lastCnt { - backoff = backoffMin - lastCnt = cnt - } - - // Backoff. - backoffT.Reset(backoff) - if backoff < backoffMax { - backoff *= backoffMul - if backoff > backoffMax { - backoff = backoffMax - } - } - select { - case <-backoffT.C: - case _, _ = <-db.closeC: - db.logf("%s exiting", name) + if errors.IsCorrupted(err) { + db.logf("%s exiting (corruption detected)", name) db.compactionExitTransact() } + + if !disableBackoff { + // Reset backoff duration if counter is advancing. + if cnt > lastCnt { + backoff = backoffMin + lastCnt = cnt + } + + // Backoff. + backoffT.Reset(backoff) + if backoff < backoffMax { + backoff *= backoffMul + if backoff > backoffMax { + backoff = backoffMax + } + } + select { + case <-backoffT.C: + case _, _ = <-db.closeC: + db.logf("%s exiting", name) + db.compactionExitTransact() + } + } } } +type compactionTransactFunc struct { + runFunc func(cnt *compactionTransactCounter) error + revertFunc func() error +} + +func (t *compactionTransactFunc) run(cnt *compactionTransactCounter) error { + return t.runFunc(cnt) +} + +func (t *compactionTransactFunc) revert() error { + if t.revertFunc != nil { + return t.revertFunc() + } + return nil +} + +func (db *DB) compactionTransactFunc(name string, run func(cnt *compactionTransactCounter) error, revert func() error) { + db.compactionTransact(name, &compactionTransactFunc{run, revert}) +} + func (db *DB) compactionExitTransact() { panic(errCompactionTransactExiting) } @@ -232,20 +307,23 @@ func (db *DB) memCompaction() { } // Pause table compaction. - ch := make(chan struct{}) + resumeC := make(chan struct{}) select { - case db.tcompPauseC <- (chan<- struct{})(ch): + case db.tcompPauseC <- (chan<- struct{})(resumeC): + case <-db.compPerErrC: + close(resumeC) + resumeC = nil case _, _ = <-db.closeC: return } - db.compactionTransact("mem@flush", func(cnt *compactionTransactCounter) (err error) { + db.compactionTransactFunc("mem@flush", func(cnt *compactionTransactCounter) (err error) { stats.startTimer() defer stats.stopTimer() return c.flush(mem.mdb, -1) }, func() error { for _, r := range c.rec.addedTables { - db.logf("mem@flush rollback @%d", r.num) + db.logf("mem@flush revert @%d", r.num) f := db.s.getTableFile(r.num) if err := f.Remove(); err != nil { return err @@ -254,7 +332,7 @@ func (db *DB) memCompaction() { return nil }) - db.compactionTransact("mem@commit", func(cnt *compactionTransactCounter) (err error) { + db.compactionTransactFunc("mem@commit", func(cnt *compactionTransactCounter) (err error) { stats.startTimer() defer stats.stopTimer() return c.commit(db.journalFile.Num(), db.frozenSeq) @@ -271,26 +349,223 @@ func (db *DB) memCompaction() { db.dropFrozenMem() // Resume table compaction. - select { - case <-ch: - case _, _ = <-db.closeC: - return + if resumeC != nil { + select { + case <-resumeC: + close(resumeC) + case _, _ = <-db.closeC: + return + } } // Trigger table compaction. - db.compTrigger(db.mcompTriggerC) + db.compSendTrigger(db.tcompCmdC) +} + +type tableCompactionBuilder struct { + db *DB + s *session + c *compaction + rec *sessionRecord + stat0, stat1 *cStatsStaging + + snapHasLastUkey bool + snapLastUkey []byte + snapLastSeq uint64 + snapIter int + snapKerrCnt int + snapDropCnt int + + kerrCnt int + dropCnt int + + minSeq uint64 + strict bool + tableSize int + + tw *tWriter +} + +func (b *tableCompactionBuilder) appendKV(key, value []byte) error { + // Create new table if not already. + if b.tw == nil { + // Check for pause event. + if b.db != nil { + select { + case ch := <-b.db.tcompPauseC: + b.db.pauseCompaction(ch) + case _, _ = <-b.db.closeC: + b.db.compactionExitTransact() + default: + } + } + + // Create new table. + var err error + b.tw, err = b.s.tops.create() + if err != nil { + return err + } + } + + // Write key/value into table. + return b.tw.append(key, value) +} + +func (b *tableCompactionBuilder) needFlush() bool { + return b.tw.tw.BytesLen() >= b.tableSize +} + +func (b *tableCompactionBuilder) flush() error { + t, err := b.tw.finish() + if err != nil { + return err + } + b.rec.addTableFile(b.c.level+1, t) + b.stat1.write += t.size + b.s.logf("table@build created L%d@%d N·%d S·%s %q:%q", b.c.level+1, t.file.Num(), b.tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax) + b.tw = nil + return nil +} + +func (b *tableCompactionBuilder) cleanup() { + if b.tw != nil { + b.tw.drop() + b.tw = nil + } +} + +func (b *tableCompactionBuilder) run(cnt *compactionTransactCounter) error { + snapResumed := b.snapIter > 0 + hasLastUkey := b.snapHasLastUkey // The key might has zero length, so this is necessary. + lastUkey := append([]byte{}, b.snapLastUkey...) + lastSeq := b.snapLastSeq + b.kerrCnt = b.snapKerrCnt + b.dropCnt = b.snapDropCnt + // Restore compaction state. + b.c.restore() + + defer b.cleanup() + + b.stat1.startTimer() + defer b.stat1.stopTimer() + + iter := b.c.newIterator() + defer iter.Release() + for i := 0; iter.Next(); i++ { + // Incr transact counter. + cnt.incr() + + // Skip until last state. + if i < b.snapIter { + continue + } + + resumed := false + if snapResumed { + resumed = true + snapResumed = false + } + + ikey := iter.Key() + ukey, seq, kt, kerr := parseIkey(ikey) + + if kerr == nil { + shouldStop := !resumed && b.c.shouldStopBefore(ikey) + + if !hasLastUkey || b.s.icmp.uCompare(lastUkey, ukey) != 0 { + // First occurrence of this user key. + + // Only rotate tables if ukey doesn't hop across. + if b.tw != nil && (shouldStop || b.needFlush()) { + if err := b.flush(); err != nil { + return err + } + + // Creates snapshot of the state. + b.c.save() + b.snapHasLastUkey = hasLastUkey + b.snapLastUkey = append(b.snapLastUkey[:0], lastUkey...) + b.snapLastSeq = lastSeq + b.snapIter = i + b.snapKerrCnt = b.kerrCnt + b.snapDropCnt = b.dropCnt + } + + hasLastUkey = true + lastUkey = append(lastUkey[:0], ukey...) + lastSeq = kMaxSeq + } + + switch { + case lastSeq <= b.minSeq: + // Dropped because newer entry for same user key exist + fallthrough // (A) + case kt == ktDel && seq <= b.minSeq && b.c.baseLevelForKey(lastUkey): + // For this user key: + // (1) there is no data in higher levels + // (2) data in lower levels will have larger seq numbers + // (3) data in layers that are being compacted here and have + // smaller seq numbers will be dropped in the next + // few iterations of this loop (by rule (A) above). + // Therefore this deletion marker is obsolete and can be dropped. + lastSeq = seq + b.dropCnt++ + continue + default: + lastSeq = seq + } + } else { + if b.strict { + return kerr + } + + // Don't drop corrupted keys. + hasLastUkey = false + lastUkey = lastUkey[:0] + lastSeq = kMaxSeq + b.kerrCnt++ + } + + if err := b.appendKV(ikey, iter.Value()); err != nil { + return err + } + } + + if err := iter.Error(); err != nil { + return err + } + + // Finish last table. + if b.tw != nil && !b.tw.empty() { + return b.flush() + } + return nil +} + +func (b *tableCompactionBuilder) revert() error { + for _, at := range b.rec.addedTables { + b.s.logf("table@build revert @%d", at.num) + f := b.s.getTableFile(at.num) + if err := f.Remove(); err != nil { + return err + } + } + return nil } func (db *DB) tableCompaction(c *compaction, noTrivial bool) { - rec := new(sessionRecord) - rec.addCompactionPointer(c.level, c.imax) + defer c.release() + + rec := &sessionRecord{numLevel: db.s.o.GetNumLevel()} + rec.addCompPtr(c.level, c.imax) if !noTrivial && c.trivial() { t := c.tables[0][0] db.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1) - rec.deleteTable(c.level, t.file.Num()) + rec.delTable(c.level, t.file.Num()) rec.addTableFile(c.level+1, t) - db.compactionTransact("table@move", func(cnt *compactionTransactCounter) (err error) { + db.compactionTransactFunc("table@move", func(cnt *compactionTransactCounter) (err error) { return db.s.commit(rec) }, nil) return @@ -301,184 +576,34 @@ func (db *DB) tableCompaction(c *compaction, noTrivial bool) { for _, t := range tables { stats[i].read += t.size // Insert deleted tables into record - rec.deleteTable(c.level+i, t.file.Num()) + rec.delTable(c.level+i, t.file.Num()) } } sourceSize := int(stats[0].read + stats[1].read) minSeq := db.minSeq() db.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq) - var snapUkey []byte - var snapHasUkey bool - var snapSeq uint64 - var snapIter int - var snapDropCnt int - var dropCnt int - db.compactionTransact("table@build", func(cnt *compactionTransactCounter) (err error) { - ukey := append([]byte{}, snapUkey...) - hasUkey := snapHasUkey - lseq := snapSeq - dropCnt = snapDropCnt - snapSched := snapIter == 0 - - var tw *tWriter - finish := func() error { - t, err := tw.finish() - if err != nil { - return err - } - rec.addTableFile(c.level+1, t) - stats[1].write += t.size - db.logf("table@build created L%d@%d N·%d S·%s %q:%q", c.level+1, t.file.Num(), tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax) - return nil - } - - defer func() { - stats[1].stopTimer() - if tw != nil { - tw.drop() - tw = nil - } - }() - - stats[1].startTimer() - iter := c.newIterator() - defer iter.Release() - for i := 0; iter.Next(); i++ { - // Incr transact counter. - cnt.incr() - - // Skip until last state. - if i < snapIter { - continue - } - - ikey := iKey(iter.Key()) - - if c.shouldStopBefore(ikey) && tw != nil { - err = finish() - if err != nil { - return - } - snapSched = true - tw = nil - } - - // Scheduled for snapshot, snapshot will used to retry compaction - // if error occured. - if snapSched { - snapUkey = append(snapUkey[:0], ukey...) - snapHasUkey = hasUkey - snapSeq = lseq - snapIter = i - snapDropCnt = dropCnt - snapSched = false - } - - if seq, vt, ok := ikey.parseNum(); !ok { - // Don't drop error keys - ukey = ukey[:0] - hasUkey = false - lseq = kMaxSeq - } else { - if !hasUkey || db.s.icmp.uCompare(ikey.ukey(), ukey) != 0 { - // First occurrence of this user key - ukey = append(ukey[:0], ikey.ukey()...) - hasUkey = true - lseq = kMaxSeq - } - - drop := false - if lseq <= minSeq { - // Dropped because newer entry for same user key exist - drop = true // (A) - } else if vt == tDel && seq <= minSeq && c.baseLevelForKey(ukey) { - // For this user key: - // (1) there is no data in higher levels - // (2) data in lower levels will have larger seq numbers - // (3) data in layers that are being compacted here and have - // smaller seq numbers will be dropped in the next - // few iterations of this loop (by rule (A) above). - // Therefore this deletion marker is obsolete and can be dropped. - drop = true - } - - lseq = seq - if drop { - dropCnt++ - continue - } - } - - // Create new table if not already - if tw == nil { - // Check for pause event. - select { - case ch := <-db.tcompPauseC: - db.pauseCompaction(ch) - case _, _ = <-db.closeC: - db.compactionExitTransact() - default: - } - - // Create new table. - tw, err = db.s.tops.create() - if err != nil { - return - } - } - - // Write key/value into table - err = tw.append(ikey, iter.Value()) - if err != nil { - return - } - - // Finish table if it is big enough - if tw.tw.BytesLen() >= kMaxTableSize { - err = finish() - if err != nil { - return - } - snapSched = true - tw = nil - } - } - - err = iter.Error() - if err != nil { - return - } - - // Finish last table - if tw != nil && !tw.empty() { - err = finish() - if err != nil { - return - } - tw = nil - } - return - }, func() error { - for _, r := range rec.addedTables { - db.logf("table@build rollback @%d", r.num) - f := db.s.getTableFile(r.num) - if err := f.Remove(); err != nil { - return err - } - } - return nil - }) + b := &tableCompactionBuilder{ + db: db, + s: db.s, + c: c, + rec: rec, + stat1: &stats[1], + minSeq: minSeq, + strict: db.s.o.GetStrict(opt.StrictCompaction), + tableSize: db.s.o.GetCompactionTableSize(c.level + 1), + } + db.compactionTransact("table@build", b) // Commit changes - db.compactionTransact("table@commit", func(cnt *compactionTransactCounter) (err error) { + db.compactionTransactFunc("table@commit", func(cnt *compactionTransactCounter) (err error) { stats[1].startTimer() defer stats[1].stopTimer() return db.s.commit(rec) }, nil) resultSize := int(stats[1].write) - db.logf("table@compaction committed F%s S%s D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), dropCnt, stats[1].duration) + db.logf("table@compaction committed F%s S%s Ke·%d D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), b.kerrCnt, b.dropCnt, stats[1].duration) // Save compaction stats for i := range stats { @@ -494,14 +619,14 @@ func (db *DB) tableRangeCompaction(level int, umin, umax []byte) { db.tableCompaction(c, true) } } else { - v := db.s.version_NB() - + v := db.s.version() m := 1 for i, t := range v.tables[1:] { if t.overlaps(db.s.icmp, umin, umax, false) { m = i + 1 } } + v.release() for level := 0; level < m; level++ { if c := db.s.getCompactionRange(level, umin, umax); c != nil { @@ -518,7 +643,9 @@ func (db *DB) tableAutoCompaction() { } func (db *DB) tableNeedCompaction() bool { - return db.s.version_NB().needCompaction() + v := db.s.version() + defer v.release() + return v.needCompaction() } func (db *DB) pauseCompaction(ch chan<- struct{}) { @@ -538,7 +665,12 @@ type cIdle struct { } func (r cIdle) ack(err error) { - r.ackC <- err + if r.ackC != nil { + defer func() { + recover() + }() + r.ackC <- err + } } type cRange struct { @@ -548,29 +680,45 @@ type cRange struct { } func (r cRange) ack(err error) { - defer func() { - recover() - }() if r.ackC != nil { + defer func() { + recover() + }() r.ackC <- err } } -func (db *DB) compSendIdle(compC chan<- cCmd) error { +// This will trigger auto compation and/or wait for all compaction to be done. +func (db *DB) compSendIdle(compC chan<- cCmd) (err error) { ch := make(chan error) defer close(ch) // Send cmd. select { case compC <- cIdle{ch}: - case err := <-db.compErrC: - return err + case err = <-db.compErrC: + return case _, _ = <-db.closeC: return ErrClosed } // Wait cmd. - return <-ch + select { + case err = <-ch: + case err = <-db.compErrC: + case _, _ = <-db.closeC: + return ErrClosed + } + return err } +// This will trigger auto compaction but will not wait for it. +func (db *DB) compSendTrigger(compC chan<- cCmd) { + select { + case compC <- cIdle{}: + default: + } +} + +// Send range compaction request. func (db *DB) compSendRange(compC chan<- cCmd, level int, min, max []byte) (err error) { ch := make(chan error) defer close(ch) @@ -584,19 +732,14 @@ func (db *DB) compSendRange(compC chan<- cCmd, level int, min, max []byte) (err } // Wait cmd. select { - case err = <-db.compErrC: case err = <-ch: + case err = <-db.compErrC: + case _, _ = <-db.closeC: + return ErrClosed } return err } -func (db *DB) compTrigger(compTriggerC chan struct{}) { - select { - case compTriggerC <- struct{}{}: - default: - } -} - func (db *DB) mCompaction() { var x cCmd @@ -615,11 +758,14 @@ func (db *DB) mCompaction() { for { select { case x = <-db.mcompCmdC: - db.memCompaction() - x.ack(nil) - x = nil - case <-db.mcompTriggerC: - db.memCompaction() + switch x.(type) { + case cIdle: + db.memCompaction() + x.ack(nil) + x = nil + default: + panic("leveldb: unknown command") + } case _, _ = <-db.closeC: return } @@ -650,7 +796,6 @@ func (db *DB) tCompaction() { if db.tableNeedCompaction() { select { case x = <-db.tcompCmdC: - case <-db.tcompTriggerC: case ch := <-db.tcompPauseC: db.pauseCompaction(ch) continue @@ -666,7 +811,6 @@ func (db *DB) tCompaction() { ackQ = ackQ[:0] select { case x = <-db.tcompCmdC: - case <-db.tcompTriggerC: case ch := <-db.tcompPauseC: db.pauseCompaction(ch) continue @@ -681,6 +825,8 @@ func (db *DB) tCompaction() { case cRange: db.tableRangeCompaction(cmd.level, cmd.min, cmd.max) x.ack(nil) + default: + panic("leveldb: unknown command") } x = nil } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go index 49c44059..4607e5da 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_iter.go @@ -48,7 +48,7 @@ func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It i = append(i, fmi) } i = append(i, ti...) - strict := db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) + strict := opt.GetStrict(db.s.o.Options, ro, opt.StrictReader) mi := iterator.NewMergedIterator(i, db.s.icmp, strict) mi.SetReleaser(&versionReleaser{v: v}) return mi @@ -59,10 +59,10 @@ func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *d if slice != nil { islice = &util.Range{} if slice.Start != nil { - islice.Start = newIKey(slice.Start, kMaxSeq, tSeek) + islice.Start = newIkey(slice.Start, kMaxSeq, ktSeek) } if slice.Limit != nil { - islice.Limit = newIKey(slice.Limit, kMaxSeq, tSeek) + islice.Limit = newIkey(slice.Limit, kMaxSeq, ktSeek) } } rawIter := db.newRawIterator(islice, ro) @@ -71,7 +71,7 @@ func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *d icmp: db.s.icmp, iter: rawIter, seq: seq, - strict: db.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator), + strict: opt.GetStrict(db.s.o.Options, ro, opt.StrictReader), key: make([]byte, 0), value: make([]byte, 0), } @@ -162,7 +162,7 @@ func (i *dbIter) Seek(key []byte) bool { return false } - ikey := newIKey(key, i.seq, tSeek) + ikey := newIkey(key, i.seq, ktSeek) if i.iter.Seek(ikey) { i.dir = dirSOI return i.next() @@ -174,15 +174,14 @@ func (i *dbIter) Seek(key []byte) bool { func (i *dbIter) next() bool { for { - ukey, seq, t, ok := parseIkey(i.iter.Key()) - if ok { + if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil { if seq <= i.seq { - switch t { - case tDel: + switch kt { + case ktDel: // Skip deleted key. i.key = append(i.key[:0], ukey...) i.dir = dirForward - case tVal: + case ktVal: if i.dir == dirSOI || i.icmp.uCompare(ukey, i.key) > 0 { i.key = append(i.key[:0], ukey...) i.value = append(i.value[:0], i.iter.Value()...) @@ -192,7 +191,7 @@ func (i *dbIter) next() bool { } } } else if i.strict { - i.setErr(errInvalidIkey) + i.setErr(kerr) break } if !i.iter.Next() { @@ -225,20 +224,19 @@ func (i *dbIter) prev() bool { del := true if i.iter.Valid() { for { - ukey, seq, t, ok := parseIkey(i.iter.Key()) - if ok { + if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil { if seq <= i.seq { if !del && i.icmp.uCompare(ukey, i.key) < 0 { return true } - del = (t == tDel) + del = (kt == ktDel) if !del { i.key = append(i.key[:0], ukey...) i.value = append(i.value[:0], i.iter.Value()...) } } } else if i.strict { - i.setErr(errInvalidIkey) + i.setErr(kerr) return false } if !i.iter.Prev() { @@ -267,13 +265,12 @@ func (i *dbIter) Prev() bool { return i.Last() case dirForward: for i.iter.Prev() { - ukey, _, _, ok := parseIkey(i.iter.Key()) - if ok { + if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil { if i.icmp.uCompare(ukey, i.key) < 0 { goto cont } } else if i.strict { - i.setErr(errInvalidIkey) + i.setErr(kerr) return false } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go index fb1ce85b..e41dcc65 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go @@ -7,6 +7,7 @@ package leveldb import ( + "container/list" "runtime" "sync" "sync/atomic" @@ -19,51 +20,41 @@ import ( type snapshotElement struct { seq uint64 ref int - // Next and previous pointers in the doubly-linked list of elements. - next, prev *snapshotElement -} - -// Initialize the snapshot. -func (db *DB) initSnapshot() { - db.snapsRoot.next = &db.snapsRoot - db.snapsRoot.prev = &db.snapsRoot + e *list.Element } // Acquires a snapshot, based on latest sequence. func (db *DB) acquireSnapshot() *snapshotElement { db.snapsMu.Lock() + defer db.snapsMu.Unlock() + seq := db.getSeq() - elem := db.snapsRoot.prev - if elem == &db.snapsRoot || elem.seq != seq { - at := db.snapsRoot.prev - next := at.next - elem = &snapshotElement{ - seq: seq, - prev: at, - next: next, + + if e := db.snapsList.Back(); e != nil { + se := e.Value.(*snapshotElement) + if se.seq == seq { + se.ref++ + return se + } else if seq < se.seq { + panic("leveldb: sequence number is not increasing") } - at.next = elem - next.prev = elem } - elem.ref++ - db.snapsMu.Unlock() - return elem + se := &snapshotElement{seq: seq, ref: 1} + se.e = db.snapsList.PushBack(se) + return se } // Releases given snapshot element. -func (db *DB) releaseSnapshot(elem *snapshotElement) { - if !db.isClosed() { - db.snapsMu.Lock() - elem.ref-- - if elem.ref == 0 { - elem.prev.next = elem.next - elem.next.prev = elem.prev - elem.next = nil - elem.prev = nil - } else if elem.ref < 0 { - panic("leveldb: Snapshot: negative element reference") - } - db.snapsMu.Unlock() +func (db *DB) releaseSnapshot(se *snapshotElement) { + db.snapsMu.Lock() + defer db.snapsMu.Unlock() + + se.ref-- + if se.ref == 0 { + db.snapsList.Remove(se.e) + se.e = nil + } else if se.ref < 0 { + panic("leveldb: Snapshot: negative element reference") } } @@ -71,10 +62,11 @@ func (db *DB) releaseSnapshot(elem *snapshotElement) { func (db *DB) minSeq() uint64 { db.snapsMu.Lock() defer db.snapsMu.Unlock() - elem := db.snapsRoot.prev - if elem != &db.snapsRoot { - return elem.seq + + if e := db.snapsList.Front(); e != nil { + return e.Value.(*snapshotElement).seq } + return db.getSeq() } @@ -98,7 +90,7 @@ func (db *DB) newSnapshot() *Snapshot { } // Get gets the value for the given key. It returns ErrNotFound if -// the DB does not contain the key. +// the DB does not contains the key. // // The caller should not modify the contents of the returned slice, but // it is safe to modify the contents of the argument after Get returns. @@ -116,6 +108,23 @@ func (snap *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err er return snap.db.get(key, snap.elem.seq, ro) } +// Has returns true if the DB does contains the given key. +// +// It is safe to modify the contents of the argument after Get returns. +func (snap *Snapshot) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) { + err = snap.db.ok() + if err != nil { + return + } + snap.mu.RLock() + defer snap.mu.RUnlock() + if snap.released { + err = ErrSnapshotReleased + return + } + return snap.db.has(key, snap.elem.seq, ro) +} + // NewIterator returns an iterator for the snapshot of the uderlying DB. // The returned iterator is not goroutine-safe, but it is safe to use // multiple iterators concurrently, with each in a dedicated goroutine. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go index b7190681..0d51534b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_test.go @@ -7,6 +7,10 @@ package leveldb import ( + "bytes" + "container/list" + crand "crypto/rand" + "encoding/binary" "fmt" "math/rand" "os" @@ -20,6 +24,7 @@ import ( "unsafe" "github.com/syndtr/goleveldb/leveldb/comparer" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" @@ -148,7 +153,10 @@ func (h *dbHarness) maxNextLevelOverlappingBytes(want uint64) { t := h.t db := h.db - var res uint64 + var ( + maxOverlaps uint64 + maxLevel int + ) v := db.s.version() for i, tt := range v.tables[1 : len(v.tables)-1] { level := i + 1 @@ -156,15 +164,18 @@ func (h *dbHarness) maxNextLevelOverlappingBytes(want uint64) { for _, t := range tt { r := next.getOverlaps(nil, db.s.icmp, t.imin.ukey(), t.imax.ukey(), false) sum := r.size() - if sum > res { - res = sum + if sum > maxOverlaps { + maxOverlaps = sum + maxLevel = level } } } v.release() - if res > want { - t.Errorf("next level overlapping bytes is more than %d, got=%d", want, res) + if maxOverlaps > want { + t.Errorf("next level most overlapping bytes is more than %d, got=%d level=%d", want, maxOverlaps, maxLevel) + } else { + t.Logf("next level most overlapping bytes is %d, level=%d want=%d", maxOverlaps, maxLevel, want) } } @@ -237,7 +248,7 @@ func (h *dbHarness) allEntriesFor(key, want string) { db := h.db s := db.s - ikey := newIKey([]byte(key), kMaxSeq, tVal) + ikey := newIkey([]byte(key), kMaxSeq, ktVal) iter := db.newRawIterator(nil, nil) if !iter.Seek(ikey) && iter.Error() != nil { t.Error("AllEntries: error during seek, err: ", iter.Error()) @@ -246,19 +257,18 @@ func (h *dbHarness) allEntriesFor(key, want string) { res := "[ " first := true for iter.Valid() { - rkey := iKey(iter.Key()) - if _, t, ok := rkey.parseNum(); ok { - if s.icmp.uCompare(ikey.ukey(), rkey.ukey()) != 0 { + if ukey, _, kt, kerr := parseIkey(iter.Key()); kerr == nil { + if s.icmp.uCompare(ikey.ukey(), ukey) != 0 { break } if !first { res += ", " } first = false - switch t { - case tVal: + switch kt { + case ktVal: res += string(iter.Value()) - case tDel: + case ktDel: res += "DEL" } } else { @@ -323,6 +333,8 @@ func (h *dbHarness) compactMem() { t := h.t db := h.db + t.Log("starting memdb compaction") + db.writeLockC <- struct{}{} defer func() { <-db.writeLockC @@ -338,6 +350,8 @@ func (h *dbHarness) compactMem() { if h.totalTables() == 0 { t.Error("zero tables after mem compaction") } + + t.Log("memdb compaction done") } func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool) { @@ -352,6 +366,8 @@ func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool) _max = []byte(max) } + t.Logf("starting table range compaction: level=%d, min=%q, max=%q", level, min, max) + if err := db.compSendRange(db.tcompCmdC, level, _min, _max); err != nil { if wanterr { t.Log("CompactRangeAt: got error (expected): ", err) @@ -361,6 +377,8 @@ func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool) } else if wanterr { t.Error("CompactRangeAt: expect error") } + + t.Log("table range compaction done") } func (h *dbHarness) compactRangeAt(level int, min, max string) { @@ -371,6 +389,8 @@ func (h *dbHarness) compactRange(min, max string) { t := h.t db := h.db + t.Logf("starting DB range compaction: min=%q, max=%q", min, max) + var r util.Range if min != "" { r.Start = []byte(min) @@ -381,6 +401,8 @@ func (h *dbHarness) compactRange(min, max string) { if err := db.CompactRange(r); err != nil { t.Error("CompactRange: got error: ", err) } + + t.Log("DB range compaction done") } func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) { @@ -502,13 +524,13 @@ func Test_FieldsAligned(t *testing.T) { p1 := new(DB) testAligned(t, "DB.seq", unsafe.Offsetof(p1.seq)) p2 := new(session) - testAligned(t, "session.stFileNum", unsafe.Offsetof(p2.stFileNum)) + testAligned(t, "session.stNextFileNum", unsafe.Offsetof(p2.stNextFileNum)) testAligned(t, "session.stJournalNum", unsafe.Offsetof(p2.stJournalNum)) testAligned(t, "session.stPrevJournalNum", unsafe.Offsetof(p2.stPrevJournalNum)) - testAligned(t, "session.stSeq", unsafe.Offsetof(p2.stSeq)) + testAligned(t, "session.stSeqNum", unsafe.Offsetof(p2.stSeqNum)) } -func TestDb_Locking(t *testing.T) { +func TestDB_Locking(t *testing.T) { h := newDbHarness(t) defer h.stor.Close() h.openAssert(false) @@ -516,7 +538,7 @@ func TestDb_Locking(t *testing.T) { h.openAssert(true) } -func TestDb_Empty(t *testing.T) { +func TestDB_Empty(t *testing.T) { trun(t, func(h *dbHarness) { h.get("foo", false) @@ -525,7 +547,7 @@ func TestDb_Empty(t *testing.T) { }) } -func TestDb_ReadWrite(t *testing.T) { +func TestDB_ReadWrite(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.getVal("foo", "v1") @@ -540,7 +562,7 @@ func TestDb_ReadWrite(t *testing.T) { }) } -func TestDb_PutDeleteGet(t *testing.T) { +func TestDB_PutDeleteGet(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.getVal("foo", "v1") @@ -554,7 +576,7 @@ func TestDb_PutDeleteGet(t *testing.T) { }) } -func TestDb_EmptyBatch(t *testing.T) { +func TestDB_EmptyBatch(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -566,7 +588,7 @@ func TestDb_EmptyBatch(t *testing.T) { h.get("foo", false) } -func TestDb_GetFromFrozen(t *testing.T) { +func TestDB_GetFromFrozen(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100100}) defer h.close() @@ -592,7 +614,7 @@ func TestDb_GetFromFrozen(t *testing.T) { h.get("k2", true) } -func TestDb_GetFromTable(t *testing.T) { +func TestDB_GetFromTable(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.compactMem() @@ -600,7 +622,7 @@ func TestDb_GetFromTable(t *testing.T) { }) } -func TestDb_GetSnapshot(t *testing.T) { +func TestDB_GetSnapshot(t *testing.T) { trun(t, func(h *dbHarness) { bar := strings.Repeat("b", 200) h.put("foo", "v1") @@ -634,7 +656,7 @@ func TestDb_GetSnapshot(t *testing.T) { }) } -func TestDb_GetLevel0Ordering(t *testing.T) { +func TestDB_GetLevel0Ordering(t *testing.T) { trun(t, func(h *dbHarness) { for i := 0; i < 4; i++ { h.put("bar", fmt.Sprintf("b%d", i)) @@ -657,7 +679,7 @@ func TestDb_GetLevel0Ordering(t *testing.T) { }) } -func TestDb_GetOrderedByLevels(t *testing.T) { +func TestDB_GetOrderedByLevels(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.compactMem() @@ -669,7 +691,7 @@ func TestDb_GetOrderedByLevels(t *testing.T) { }) } -func TestDb_GetPicksCorrectFile(t *testing.T) { +func TestDB_GetPicksCorrectFile(t *testing.T) { trun(t, func(h *dbHarness) { // Arrange to have multiple files in a non-level-0 level. h.put("a", "va") @@ -693,7 +715,7 @@ func TestDb_GetPicksCorrectFile(t *testing.T) { }) } -func TestDb_GetEncountersEmptyLevel(t *testing.T) { +func TestDB_GetEncountersEmptyLevel(t *testing.T) { trun(t, func(h *dbHarness) { // Arrange for the following to happen: // * sstable A in level 0 @@ -748,7 +770,7 @@ func TestDb_GetEncountersEmptyLevel(t *testing.T) { }) } -func TestDb_IterMultiWithDelete(t *testing.T) { +func TestDB_IterMultiWithDelete(t *testing.T) { trun(t, func(h *dbHarness) { h.put("a", "va") h.put("b", "vb") @@ -774,7 +796,7 @@ func TestDb_IterMultiWithDelete(t *testing.T) { }) } -func TestDb_IteratorPinsRef(t *testing.T) { +func TestDB_IteratorPinsRef(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -798,7 +820,7 @@ func TestDb_IteratorPinsRef(t *testing.T) { iter.Release() } -func TestDb_Recover(t *testing.T) { +func TestDB_Recover(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.put("baz", "v5") @@ -820,7 +842,7 @@ func TestDb_Recover(t *testing.T) { }) } -func TestDb_RecoverWithEmptyJournal(t *testing.T) { +func TestDB_RecoverWithEmptyJournal(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") h.put("foo", "v2") @@ -834,7 +856,7 @@ func TestDb_RecoverWithEmptyJournal(t *testing.T) { }) } -func TestDb_RecoverDuringMemtableCompaction(t *testing.T) { +func TestDB_RecoverDuringMemtableCompaction(t *testing.T) { truno(t, &opt.Options{WriteBuffer: 1000000}, func(h *dbHarness) { h.stor.DelaySync(storage.TypeTable) @@ -850,7 +872,7 @@ func TestDb_RecoverDuringMemtableCompaction(t *testing.T) { }) } -func TestDb_MinorCompactionsHappen(t *testing.T) { +func TestDB_MinorCompactionsHappen(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 10000}) defer h.close() @@ -874,7 +896,7 @@ func TestDb_MinorCompactionsHappen(t *testing.T) { } } -func TestDb_RecoverWithLargeJournal(t *testing.T) { +func TestDB_RecoverWithLargeJournal(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -899,7 +921,7 @@ func TestDb_RecoverWithLargeJournal(t *testing.T) { v.release() } -func TestDb_CompactionsGenerateMultipleFiles(t *testing.T) { +func TestDB_CompactionsGenerateMultipleFiles(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{ WriteBuffer: 10000000, Compression: opt.NoCompression, @@ -937,11 +959,11 @@ func TestDb_CompactionsGenerateMultipleFiles(t *testing.T) { } } -func TestDb_RepeatedWritesToSameKey(t *testing.T) { +func TestDB_RepeatedWritesToSameKey(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100000}) defer h.close() - maxTables := kNumLevels + kL0_StopWritesTrigger + maxTables := h.o.GetNumLevel() + h.o.GetWriteL0PauseTrigger() value := strings.Repeat("v", 2*h.o.GetWriteBuffer()) for i := 0; i < 5*maxTables; i++ { @@ -953,13 +975,13 @@ func TestDb_RepeatedWritesToSameKey(t *testing.T) { } } -func TestDb_RepeatedWritesToSameKeyAfterReopen(t *testing.T) { +func TestDB_RepeatedWritesToSameKeyAfterReopen(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100000}) defer h.close() h.reopenDB() - maxTables := kNumLevels + kL0_StopWritesTrigger + maxTables := h.o.GetNumLevel() + h.o.GetWriteL0PauseTrigger() value := strings.Repeat("v", 2*h.o.GetWriteBuffer()) for i := 0; i < 5*maxTables; i++ { @@ -971,11 +993,11 @@ func TestDb_RepeatedWritesToSameKeyAfterReopen(t *testing.T) { } } -func TestDb_SparseMerge(t *testing.T) { +func TestDB_SparseMerge(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression}) defer h.close() - h.putMulti(kNumLevels, "A", "Z") + h.putMulti(h.o.GetNumLevel(), "A", "Z") // Suppose there is: // small amount of data with prefix A @@ -999,6 +1021,7 @@ func TestDb_SparseMerge(t *testing.T) { h.put("C", "vc2") h.compactMem() + h.waitCompaction() h.maxNextLevelOverlappingBytes(20 * 1048576) h.compactRangeAt(0, "", "") h.waitCompaction() @@ -1008,7 +1031,7 @@ func TestDb_SparseMerge(t *testing.T) { h.maxNextLevelOverlappingBytes(20 * 1048576) } -func TestDb_SizeOf(t *testing.T) { +func TestDB_SizeOf(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{ Compression: opt.NoCompression, WriteBuffer: 10000000, @@ -1058,7 +1081,7 @@ func TestDb_SizeOf(t *testing.T) { } } -func TestDb_SizeOf_MixOfSmallAndLarge(t *testing.T) { +func TestDB_SizeOf_MixOfSmallAndLarge(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression}) defer h.close() @@ -1096,7 +1119,7 @@ func TestDb_SizeOf_MixOfSmallAndLarge(t *testing.T) { } } -func TestDb_Snapshot(t *testing.T) { +func TestDB_Snapshot(t *testing.T) { trun(t, func(h *dbHarness) { h.put("foo", "v1") s1 := h.getSnapshot() @@ -1125,13 +1148,51 @@ func TestDb_Snapshot(t *testing.T) { }) } -func TestDb_HiddenValuesAreRemoved(t *testing.T) { +func TestDB_SnapshotList(t *testing.T) { + db := &DB{snapsList: list.New()} + e0a := db.acquireSnapshot() + e0b := db.acquireSnapshot() + db.seq = 1 + e1 := db.acquireSnapshot() + db.seq = 2 + e2 := db.acquireSnapshot() + + if db.minSeq() != 0 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + db.releaseSnapshot(e0a) + if db.minSeq() != 0 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + db.releaseSnapshot(e2) + if db.minSeq() != 0 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + db.releaseSnapshot(e0b) + if db.minSeq() != 1 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + e2 = db.acquireSnapshot() + if db.minSeq() != 1 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + db.releaseSnapshot(e1) + if db.minSeq() != 2 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } + db.releaseSnapshot(e2) + if db.minSeq() != 2 { + t.Fatalf("invalid sequence number, got=%d", db.minSeq()) + } +} + +func TestDB_HiddenValuesAreRemoved(t *testing.T) { trun(t, func(h *dbHarness) { s := h.db.s h.put("foo", "v1") h.compactMem() - m := kMaxMemCompactLevel + m := h.o.GetMaxMemCompationLevel() v := s.version() num := v.tLen(m) v.release() @@ -1168,14 +1229,14 @@ func TestDb_HiddenValuesAreRemoved(t *testing.T) { }) } -func TestDb_DeletionMarkers2(t *testing.T) { +func TestDB_DeletionMarkers2(t *testing.T) { h := newDbHarness(t) defer h.close() s := h.db.s h.put("foo", "v1") h.compactMem() - m := kMaxMemCompactLevel + m := h.o.GetMaxMemCompationLevel() v := s.version() num := v.tLen(m) v.release() @@ -1209,7 +1270,7 @@ func TestDb_DeletionMarkers2(t *testing.T) { h.allEntriesFor("foo", "[ ]") } -func TestDb_CompactionTableOpenError(t *testing.T) { +func TestDB_CompactionTableOpenError(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{CachedOpenFiles: -1}) defer h.close() @@ -1228,14 +1289,14 @@ func TestDb_CompactionTableOpenError(t *testing.T) { t.Errorf("total tables is %d, want %d", n, im) } - h.stor.SetOpenErr(storage.TypeTable) + h.stor.SetEmuErr(storage.TypeTable, tsOpOpen) go h.db.CompactRange(util.Range{}) if err := h.db.compSendIdle(h.db.tcompCmdC); err != nil { t.Log("compaction error: ", err) } h.closeDB0() h.openDB() - h.stor.SetOpenErr(0) + h.stor.SetEmuErr(0, tsOpOpen) for i := 0; i < im; i++ { for j := 0; j < jm; j++ { @@ -1244,9 +1305,9 @@ func TestDb_CompactionTableOpenError(t *testing.T) { } } -func TestDb_OverlapInLevel0(t *testing.T) { +func TestDB_OverlapInLevel0(t *testing.T) { trun(t, func(h *dbHarness) { - if kMaxMemCompactLevel != 2 { + if h.o.GetMaxMemCompationLevel() != 2 { t.Fatal("fix test to reflect the config") } @@ -1287,7 +1348,7 @@ func TestDb_OverlapInLevel0(t *testing.T) { }) } -func TestDb_L0_CompactionBug_Issue44_a(t *testing.T) { +func TestDB_L0_CompactionBug_Issue44_a(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -1307,7 +1368,7 @@ func TestDb_L0_CompactionBug_Issue44_a(t *testing.T) { h.getKeyVal("(a->v)") } -func TestDb_L0_CompactionBug_Issue44_b(t *testing.T) { +func TestDB_L0_CompactionBug_Issue44_b(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -1336,7 +1397,7 @@ func TestDb_L0_CompactionBug_Issue44_b(t *testing.T) { h.getKeyVal("(->)(c->cv)") } -func TestDb_SingleEntryMemCompaction(t *testing.T) { +func TestDB_SingleEntryMemCompaction(t *testing.T) { trun(t, func(h *dbHarness) { for i := 0; i < 10; i++ { h.put("big", strings.Repeat("v", opt.DefaultWriteBuffer)) @@ -1353,7 +1414,7 @@ func TestDb_SingleEntryMemCompaction(t *testing.T) { }) } -func TestDb_ManifestWriteError(t *testing.T) { +func TestDB_ManifestWriteError(t *testing.T) { for i := 0; i < 2; i++ { func() { h := newDbHarness(t) @@ -1366,23 +1427,23 @@ func TestDb_ManifestWriteError(t *testing.T) { h.compactMem() h.getVal("foo", "bar") v := h.db.s.version() - if n := v.tLen(kMaxMemCompactLevel); n != 1 { + if n := v.tLen(h.o.GetMaxMemCompationLevel()); n != 1 { t.Errorf("invalid total tables, want=1 got=%d", n) } v.release() if i == 0 { - h.stor.SetWriteErr(storage.TypeManifest) + h.stor.SetEmuErr(storage.TypeManifest, tsOpWrite) } else { - h.stor.SetSyncErr(storage.TypeManifest) + h.stor.SetEmuErr(storage.TypeManifest, tsOpSync) } // Merging compaction (will fail) - h.compactRangeAtErr(kMaxMemCompactLevel, "", "", true) + h.compactRangeAtErr(h.o.GetMaxMemCompationLevel(), "", "", true) h.db.Close() - h.stor.SetWriteErr(0) - h.stor.SetSyncErr(0) + h.stor.SetEmuErr(0, tsOpWrite) + h.stor.SetEmuErr(0, tsOpSync) // Should not lose data h.openDB() @@ -1403,7 +1464,7 @@ func assertErr(t *testing.T, err error, wanterr bool) { } } -func TestDb_ClosedIsClosed(t *testing.T) { +func TestDB_ClosedIsClosed(t *testing.T) { h := newDbHarness(t) db := h.db @@ -1498,7 +1559,7 @@ func (p numberComparer) Compare(a, b []byte) int { func (numberComparer) Separator(dst, a, b []byte) []byte { return nil } func (numberComparer) Successor(dst, b []byte) []byte { return nil } -func TestDb_CustomComparer(t *testing.T) { +func TestDB_CustomComparer(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{ Comparer: numberComparer{}, WriteBuffer: 1000, @@ -1528,11 +1589,11 @@ func TestDb_CustomComparer(t *testing.T) { } } -func TestDb_ManualCompaction(t *testing.T) { +func TestDB_ManualCompaction(t *testing.T) { h := newDbHarness(t) defer h.close() - if kMaxMemCompactLevel != 2 { + if h.o.GetMaxMemCompationLevel() != 2 { t.Fatal("fix test to reflect the config") } @@ -1566,7 +1627,7 @@ func TestDb_ManualCompaction(t *testing.T) { h.tablesPerLevel("0,0,1") } -func TestDb_BloomFilter(t *testing.T) { +func TestDB_BloomFilter(t *testing.T) { h := newDbHarnessWopt(t, &opt.Options{ BlockCache: opt.NoCache, Filter: filter.NewBloomFilter(10), @@ -1619,7 +1680,7 @@ func TestDb_BloomFilter(t *testing.T) { h.stor.ReleaseSync(storage.TypeTable) } -func TestDb_Concurrent(t *testing.T) { +func TestDB_Concurrent(t *testing.T) { const n, secs, maxkey = 4, 2, 1000 runtime.GOMAXPROCS(n) @@ -1684,7 +1745,7 @@ func TestDb_Concurrent(t *testing.T) { runtime.GOMAXPROCS(1) } -func TestDb_Concurrent2(t *testing.T) { +func TestDB_Concurrent2(t *testing.T) { const n, n2 = 4, 4000 runtime.GOMAXPROCS(n*2 + 2) @@ -1755,7 +1816,7 @@ func TestDb_Concurrent2(t *testing.T) { runtime.GOMAXPROCS(1) } -func TestDb_CreateReopenDbOnFile(t *testing.T) { +func TestDB_CreateReopenDbOnFile(t *testing.T) { dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestCreateReopenDbOnFile-%d", os.Getuid())) if err := os.RemoveAll(dbpath); err != nil { t.Fatal("cannot remove old db: ", err) @@ -1783,7 +1844,7 @@ func TestDb_CreateReopenDbOnFile(t *testing.T) { } } -func TestDb_CreateReopenDbOnFile2(t *testing.T) { +func TestDB_CreateReopenDbOnFile2(t *testing.T) { dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestCreateReopenDbOnFile2-%d", os.Getuid())) if err := os.RemoveAll(dbpath); err != nil { t.Fatal("cannot remove old db: ", err) @@ -1804,7 +1865,7 @@ func TestDb_CreateReopenDbOnFile2(t *testing.T) { } } -func TestDb_DeletionMarkersOnMemdb(t *testing.T) { +func TestDB_DeletionMarkersOnMemdb(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -1815,8 +1876,8 @@ func TestDb_DeletionMarkersOnMemdb(t *testing.T) { h.getKeyVal("") } -func TestDb_LeveldbIssue178(t *testing.T) { - nKeys := (kMaxTableSize / 30) * 5 +func TestDB_LeveldbIssue178(t *testing.T) { + nKeys := (opt.DefaultCompactionTableSize / 30) * 5 key1 := func(i int) string { return fmt.Sprintf("my_key_%d", i) } @@ -1858,7 +1919,7 @@ func TestDb_LeveldbIssue178(t *testing.T) { h.assertNumKeys(nKeys) } -func TestDb_LeveldbIssue200(t *testing.T) { +func TestDB_LeveldbIssue200(t *testing.T) { h := newDbHarness(t) defer h.close() @@ -1884,3 +1945,635 @@ func TestDb_LeveldbIssue200(t *testing.T) { iter.Next() assertBytes(t, []byte("5"), iter.Key()) } + +func TestDB_GoleveldbIssue74(t *testing.T) { + h := newDbHarnessWopt(t, &opt.Options{ + WriteBuffer: 1 * opt.MiB, + }) + defer h.close() + + const n, dur = 10000, 5 * time.Second + + runtime.GOMAXPROCS(runtime.NumCPU()) + + until := time.Now().Add(dur) + wg := new(sync.WaitGroup) + wg.Add(2) + var done uint32 + go func() { + var i int + defer func() { + t.Logf("WRITER DONE #%d", i) + atomic.StoreUint32(&done, 1) + wg.Done() + }() + + b := new(Batch) + for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ { + iv := fmt.Sprintf("VAL%010d", i) + for k := 0; k < n; k++ { + key := fmt.Sprintf("KEY%06d", k) + b.Put([]byte(key), []byte(key+iv)) + b.Put([]byte(fmt.Sprintf("PTR%06d", k)), []byte(key)) + } + h.write(b) + + b.Reset() + snap := h.getSnapshot() + iter := snap.NewIterator(util.BytesPrefix([]byte("PTR")), nil) + var k int + for ; iter.Next(); k++ { + ptrKey := iter.Key() + key := iter.Value() + + if _, err := snap.Get(ptrKey, nil); err != nil { + t.Fatalf("WRITER #%d snapshot.Get %q: %v", i, ptrKey, err) + } + if value, err := snap.Get(key, nil); err != nil { + t.Fatalf("WRITER #%d snapshot.Get %q: %v", i, key, err) + } else if string(value) != string(key)+iv { + t.Fatalf("WRITER #%d snapshot.Get %q got invalid value, want %q got %q", i, key, string(key)+iv, value) + } + + b.Delete(key) + b.Delete(ptrKey) + } + h.write(b) + iter.Release() + snap.Release() + if k != n { + t.Fatalf("#%d %d != %d", i, k, n) + } + } + }() + go func() { + var i int + defer func() { + t.Logf("READER DONE #%d", i) + atomic.StoreUint32(&done, 1) + wg.Done() + }() + for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ { + snap := h.getSnapshot() + iter := snap.NewIterator(util.BytesPrefix([]byte("PTR")), nil) + var prevValue string + var k int + for ; iter.Next(); k++ { + ptrKey := iter.Key() + key := iter.Value() + + if _, err := snap.Get(ptrKey, nil); err != nil { + t.Fatalf("READER #%d snapshot.Get %q: %v", i, ptrKey, err) + } + + if value, err := snap.Get(key, nil); err != nil { + t.Fatalf("READER #%d snapshot.Get %q: %v", i, key, err) + } else if prevValue != "" && string(value) != string(key)+prevValue { + t.Fatalf("READER #%d snapshot.Get %q got invalid value, want %q got %q", i, key, string(key)+prevValue, value) + } else { + prevValue = string(value[len(key):]) + } + } + iter.Release() + snap.Release() + if k > 0 && k != n { + t.Fatalf("#%d %d != %d", i, k, n) + } + } + }() + wg.Wait() +} + +func TestDB_GetProperties(t *testing.T) { + h := newDbHarness(t) + defer h.close() + + _, err := h.db.GetProperty("leveldb.num-files-at-level") + if err == nil { + t.Error("GetProperty() failed to detect missing level") + } + + _, err = h.db.GetProperty("leveldb.num-files-at-level0") + if err != nil { + t.Error("got unexpected error", err) + } + + _, err = h.db.GetProperty("leveldb.num-files-at-level0x") + if err == nil { + t.Error("GetProperty() failed to detect invalid level") + } +} + +func TestDB_GoleveldbIssue72and83(t *testing.T) { + h := newDbHarnessWopt(t, &opt.Options{ + WriteBuffer: 1 * opt.MiB, + CachedOpenFiles: 3, + }) + defer h.close() + + const n, wn, dur = 10000, 100, 30 * time.Second + + runtime.GOMAXPROCS(runtime.NumCPU()) + + randomData := func(prefix byte, i int) []byte { + data := make([]byte, 1+4+32+64+32) + _, err := crand.Reader.Read(data[1 : len(data)-8]) + if err != nil { + panic(err) + } + data[0] = prefix + binary.LittleEndian.PutUint32(data[len(data)-8:], uint32(i)) + binary.LittleEndian.PutUint32(data[len(data)-4:], util.NewCRC(data[:len(data)-4]).Value()) + return data + } + + keys := make([][]byte, n) + for i := range keys { + keys[i] = randomData(1, 0) + } + + until := time.Now().Add(dur) + wg := new(sync.WaitGroup) + wg.Add(3) + var done uint32 + go func() { + i := 0 + defer func() { + t.Logf("WRITER DONE #%d", i) + wg.Done() + }() + + b := new(Batch) + for ; i < wn && atomic.LoadUint32(&done) == 0; i++ { + b.Reset() + for _, k1 := range keys { + k2 := randomData(2, i) + b.Put(k2, randomData(42, i)) + b.Put(k1, k2) + } + if err := h.db.Write(b, h.wo); err != nil { + atomic.StoreUint32(&done, 1) + t.Fatalf("WRITER #%d db.Write: %v", i, err) + } + } + }() + go func() { + var i int + defer func() { + t.Logf("READER0 DONE #%d", i) + atomic.StoreUint32(&done, 1) + wg.Done() + }() + for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ { + snap := h.getSnapshot() + seq := snap.elem.seq + if seq == 0 { + snap.Release() + continue + } + iter := snap.NewIterator(util.BytesPrefix([]byte{1}), nil) + writei := int(seq/(n*2) - 1) + var k int + for ; iter.Next(); k++ { + k1 := iter.Key() + k2 := iter.Value() + k1checksum0 := binary.LittleEndian.Uint32(k1[len(k1)-4:]) + k1checksum1 := util.NewCRC(k1[:len(k1)-4]).Value() + if k1checksum0 != k1checksum1 { + t.Fatalf("READER0 #%d.%d W#%d invalid K1 checksum: %#x != %#x", i, k, k1checksum0, k1checksum0) + } + k2checksum0 := binary.LittleEndian.Uint32(k2[len(k2)-4:]) + k2checksum1 := util.NewCRC(k2[:len(k2)-4]).Value() + if k2checksum0 != k2checksum1 { + t.Fatalf("READER0 #%d.%d W#%d invalid K2 checksum: %#x != %#x", i, k, k2checksum0, k2checksum1) + } + kwritei := int(binary.LittleEndian.Uint32(k2[len(k2)-8:])) + if writei != kwritei { + t.Fatalf("READER0 #%d.%d W#%d invalid write iteration num: %d", i, k, writei, kwritei) + } + if _, err := snap.Get(k2, nil); err != nil { + t.Fatalf("READER0 #%d.%d W#%d snap.Get: %v\nk1: %x\n -> k2: %x", i, k, writei, err, k1, k2) + } + } + if err := iter.Error(); err != nil { + t.Fatalf("READER0 #%d.%d W#%d snap.Iterator: %v", i, k, writei, err) + } + iter.Release() + snap.Release() + if k > 0 && k != n { + t.Fatalf("READER0 #%d W#%d short read, got=%d want=%d", i, writei, k, n) + } + } + }() + go func() { + var i int + defer func() { + t.Logf("READER1 DONE #%d", i) + atomic.StoreUint32(&done, 1) + wg.Done() + }() + for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ { + iter := h.db.NewIterator(nil, nil) + seq := iter.(*dbIter).seq + if seq == 0 { + iter.Release() + continue + } + writei := int(seq/(n*2) - 1) + var k int + for ok := iter.Last(); ok; ok = iter.Prev() { + k++ + } + if err := iter.Error(); err != nil { + t.Fatalf("READER1 #%d.%d W#%d db.Iterator: %v", i, k, writei, err) + } + iter.Release() + if m := (writei+1)*n + n; k != m { + t.Fatalf("READER1 #%d W#%d short read, got=%d want=%d", i, writei, k, m) + } + } + }() + + wg.Wait() +} + +func TestDB_TransientError(t *testing.T) { + h := newDbHarnessWopt(t, &opt.Options{ + WriteBuffer: 128 * opt.KiB, + CachedOpenFiles: 3, + DisableCompactionBackoff: true, + }) + defer h.close() + + const ( + nSnap = 20 + nKey = 10000 + ) + + var ( + snaps [nSnap]*Snapshot + b = &Batch{} + ) + for i := range snaps { + vtail := fmt.Sprintf("VAL%030d", i) + b.Reset() + for k := 0; k < nKey; k++ { + key := fmt.Sprintf("KEY%8d", k) + b.Put([]byte(key), []byte(key+vtail)) + } + h.stor.SetEmuRandErr(storage.TypeTable, tsOpOpen, tsOpRead, tsOpReadAt) + if err := h.db.Write(b, nil); err != nil { + t.Logf("WRITE #%d error: %v", i, err) + h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt, tsOpWrite) + for { + if err := h.db.Write(b, nil); err == nil { + break + } else if errors.IsCorrupted(err) { + t.Fatalf("WRITE #%d corrupted: %v", i, err) + } + } + } + + snaps[i] = h.db.newSnapshot() + b.Reset() + for k := 0; k < nKey; k++ { + key := fmt.Sprintf("KEY%8d", k) + b.Delete([]byte(key)) + } + h.stor.SetEmuRandErr(storage.TypeTable, tsOpOpen, tsOpRead, tsOpReadAt) + if err := h.db.Write(b, nil); err != nil { + t.Logf("WRITE #%d error: %v", i, err) + h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt) + for { + if err := h.db.Write(b, nil); err == nil { + break + } else if errors.IsCorrupted(err) { + t.Fatalf("WRITE #%d corrupted: %v", i, err) + } + } + } + } + h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt) + + runtime.GOMAXPROCS(runtime.NumCPU()) + + rnd := rand.New(rand.NewSource(0xecafdaed)) + wg := &sync.WaitGroup{} + for i, snap := range snaps { + wg.Add(2) + + go func(i int, snap *Snapshot, sk []int) { + defer wg.Done() + + vtail := fmt.Sprintf("VAL%030d", i) + for _, k := range sk { + key := fmt.Sprintf("KEY%8d", k) + xvalue, err := snap.Get([]byte(key), nil) + if err != nil { + t.Fatalf("READER_GET #%d SEQ=%d K%d error: %v", i, snap.elem.seq, k, err) + } + value := key + vtail + if !bytes.Equal([]byte(value), xvalue) { + t.Fatalf("READER_GET #%d SEQ=%d K%d invalid value: want %q, got %q", i, snap.elem.seq, k, value, xvalue) + } + } + }(i, snap, rnd.Perm(nKey)) + + go func(i int, snap *Snapshot) { + defer wg.Done() + + vtail := fmt.Sprintf("VAL%030d", i) + iter := snap.NewIterator(nil, nil) + defer iter.Release() + for k := 0; k < nKey; k++ { + if !iter.Next() { + if err := iter.Error(); err != nil { + t.Fatalf("READER_ITER #%d K%d error: %v", i, k, err) + } else { + t.Fatalf("READER_ITER #%d K%d eoi", i, k) + } + } + key := fmt.Sprintf("KEY%8d", k) + xkey := iter.Key() + if !bytes.Equal([]byte(key), xkey) { + t.Fatalf("READER_ITER #%d K%d invalid key: want %q, got %q", i, k, key, xkey) + } + value := key + vtail + xvalue := iter.Value() + if !bytes.Equal([]byte(value), xvalue) { + t.Fatalf("READER_ITER #%d K%d invalid value: want %q, got %q", i, k, value, xvalue) + } + } + }(i, snap) + } + + wg.Wait() +} + +func TestDB_UkeyShouldntHopAcrossTable(t *testing.T) { + h := newDbHarnessWopt(t, &opt.Options{ + WriteBuffer: 112 * opt.KiB, + CompactionTableSize: 90 * opt.KiB, + CompactionExpandLimitFactor: 1, + }) + defer h.close() + + const ( + nSnap = 190 + nKey = 140 + ) + + var ( + snaps [nSnap]*Snapshot + b = &Batch{} + ) + for i := range snaps { + vtail := fmt.Sprintf("VAL%030d", i) + b.Reset() + for k := 0; k < nKey; k++ { + key := fmt.Sprintf("KEY%08d", k) + b.Put([]byte(key), []byte(key+vtail)) + } + if err := h.db.Write(b, nil); err != nil { + t.Fatalf("WRITE #%d error: %v", i, err) + } + + snaps[i] = h.db.newSnapshot() + b.Reset() + for k := 0; k < nKey; k++ { + key := fmt.Sprintf("KEY%08d", k) + b.Delete([]byte(key)) + } + if err := h.db.Write(b, nil); err != nil { + t.Fatalf("WRITE #%d error: %v", i, err) + } + } + + h.compactMem() + + h.waitCompaction() + for level, tables := range h.db.s.stVersion.tables { + for _, table := range tables { + t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax) + } + } + + h.compactRangeAt(0, "", "") + h.waitCompaction() + for level, tables := range h.db.s.stVersion.tables { + for _, table := range tables { + t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax) + } + } + h.compactRangeAt(1, "", "") + h.waitCompaction() + for level, tables := range h.db.s.stVersion.tables { + for _, table := range tables { + t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax) + } + } + runtime.GOMAXPROCS(runtime.NumCPU()) + + wg := &sync.WaitGroup{} + for i, snap := range snaps { + wg.Add(1) + + go func(i int, snap *Snapshot) { + defer wg.Done() + + vtail := fmt.Sprintf("VAL%030d", i) + for k := 0; k < nKey; k++ { + key := fmt.Sprintf("KEY%08d", k) + xvalue, err := snap.Get([]byte(key), nil) + if err != nil { + t.Fatalf("READER_GET #%d SEQ=%d K%d error: %v", i, snap.elem.seq, k, err) + } + value := key + vtail + if !bytes.Equal([]byte(value), xvalue) { + t.Fatalf("READER_GET #%d SEQ=%d K%d invalid value: want %q, got %q", i, snap.elem.seq, k, value, xvalue) + } + } + }(i, snap) + } + + wg.Wait() +} + +func TestDB_TableCompactionBuilder(t *testing.T) { + stor := newTestStorage(t) + defer stor.Close() + + const nSeq = 99 + + o := &opt.Options{ + WriteBuffer: 112 * opt.KiB, + CompactionTableSize: 43 * opt.KiB, + CompactionExpandLimitFactor: 1, + CompactionGPOverlapsFactor: 1, + BlockCache: opt.NoCache, + } + s, err := newSession(stor, o) + if err != nil { + t.Fatal(err) + } + if err := s.create(); err != nil { + t.Fatal(err) + } + defer s.close() + var ( + seq uint64 + targetSize = 5 * o.CompactionTableSize + value = bytes.Repeat([]byte{'0'}, 100) + ) + for i := 0; i < 2; i++ { + tw, err := s.tops.create() + if err != nil { + t.Fatal(err) + } + for k := 0; tw.tw.BytesLen() < targetSize; k++ { + key := []byte(fmt.Sprintf("%09d", k)) + seq += nSeq - 1 + for x := uint64(0); x < nSeq; x++ { + if err := tw.append(newIkey(key, seq-x, ktVal), value); err != nil { + t.Fatal(err) + } + } + } + tf, err := tw.finish() + if err != nil { + t.Fatal(err) + } + rec := &sessionRecord{numLevel: s.o.GetNumLevel()} + rec.addTableFile(i, tf) + if err := s.commit(rec); err != nil { + t.Fatal(err) + } + } + + // Build grandparent. + v := s.version() + c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...)) + rec := &sessionRecord{numLevel: s.o.GetNumLevel()} + b := &tableCompactionBuilder{ + s: s, + c: c, + rec: rec, + stat1: new(cStatsStaging), + minSeq: 0, + strict: true, + tableSize: o.CompactionTableSize/3 + 961, + } + if err := b.run(new(compactionTransactCounter)); err != nil { + t.Fatal(err) + } + for _, t := range c.tables[0] { + rec.delTable(c.level, t.file.Num()) + } + if err := s.commit(rec); err != nil { + t.Fatal(err) + } + c.release() + + // Build level-1. + v = s.version() + c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...)) + rec = &sessionRecord{numLevel: s.o.GetNumLevel()} + b = &tableCompactionBuilder{ + s: s, + c: c, + rec: rec, + stat1: new(cStatsStaging), + minSeq: 0, + strict: true, + tableSize: o.CompactionTableSize, + } + if err := b.run(new(compactionTransactCounter)); err != nil { + t.Fatal(err) + } + for _, t := range c.tables[0] { + rec.delTable(c.level, t.file.Num()) + } + // Move grandparent to level-3 + for _, t := range v.tables[2] { + rec.delTable(2, t.file.Num()) + rec.addTableFile(3, t) + } + if err := s.commit(rec); err != nil { + t.Fatal(err) + } + c.release() + + v = s.version() + for level, want := range []bool{false, true, false, true, false} { + got := len(v.tables[level]) > 0 + if want != got { + t.Fatalf("invalid level-%d tables len: want %v, got %v", level, want, got) + } + } + for i, f := range v.tables[1][:len(v.tables[1])-1] { + nf := v.tables[1][i+1] + if bytes.Equal(f.imax.ukey(), nf.imin.ukey()) { + t.Fatalf("KEY %q hop across table %d .. %d", f.imax.ukey(), f.file.Num(), nf.file.Num()) + } + } + v.release() + + // Compaction with transient error. + v = s.version() + c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...)) + rec = &sessionRecord{numLevel: s.o.GetNumLevel()} + b = &tableCompactionBuilder{ + s: s, + c: c, + rec: rec, + stat1: new(cStatsStaging), + minSeq: 0, + strict: true, + tableSize: o.CompactionTableSize, + } + stor.SetEmuErrOnce(storage.TypeTable, tsOpSync) + stor.SetEmuRandErr(storage.TypeTable, tsOpRead, tsOpReadAt, tsOpWrite) + stor.SetEmuRandErrProb(0xf0) + for { + if err := b.run(new(compactionTransactCounter)); err != nil { + t.Logf("(expected) b.run: %v", err) + } else { + break + } + } + if err := s.commit(rec); err != nil { + t.Fatal(err) + } + c.release() + + stor.SetEmuErrOnce(0, tsOpSync) + stor.SetEmuRandErr(0, tsOpRead, tsOpReadAt, tsOpWrite) + + v = s.version() + if len(v.tables[1]) != len(v.tables[2]) { + t.Fatalf("invalid tables length, want %d, got %d", len(v.tables[1]), len(v.tables[2])) + } + for i, f0 := range v.tables[1] { + f1 := v.tables[2][i] + iter0 := s.tops.newIterator(f0, nil, nil) + iter1 := s.tops.newIterator(f1, nil, nil) + for j := 0; true; j++ { + next0 := iter0.Next() + next1 := iter1.Next() + if next0 != next1 { + t.Fatalf("#%d.%d invalid eoi: want %v, got %v", i, j, next0, next1) + } + key0 := iter0.Key() + key1 := iter1.Key() + if !bytes.Equal(key0, key1) { + t.Fatalf("#%d.%d invalid key: want %q, got %q", i, j, key0, key1) + } + if next0 == false { + break + } + } + iter0.Release() + iter1.Release() + } + v.release() +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go index 4f6b792d..a8a2bdf7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_util.go @@ -7,8 +7,7 @@ package leveldb import ( - "errors" - + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/storage" @@ -38,7 +37,9 @@ func (db *DB) logf(format string, v ...interface{}) { db.s.logf(format, v...) } // Check and clean files. func (db *DB) checkAndCleanFiles() error { - v := db.s.version_NB() + v := db.s.version() + defer v.release() + tablesMap := make(map[uint64]bool) for _, tables := range v.tables { for _, t := range tables { @@ -78,12 +79,14 @@ func (db *DB) checkAndCleanFiles() error { } if nTables != len(tablesMap) { + var missing []*storage.FileInfo for num, present := range tablesMap { if !present { + missing = append(missing, &storage.FileInfo{Type: storage.TypeTable, Num: num}) db.logf("db@janitor table missing @%d", num) } } - return ErrCorrupted{Type: MissingFiles, Err: errors.New("leveldb: table files missing")} + return errors.NewErrCorrupted(nil, &errors.ErrMissingFiles{Files: missing}) } db.logf("db@janitor F·%d G·%d", len(files), len(rem)) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go index 82725a9e..e94b7b60 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/db_write.go @@ -59,7 +59,7 @@ func (db *DB) rotateMem(n int) (mem *memDB, err error) { } // Schedule memdb compaction. - db.compTrigger(db.mcompTriggerC) + db.compSendTrigger(db.mcompCmdC) return } @@ -77,12 +77,12 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) { }() nn = mem.mdb.Free() switch { - case v.tLen(0) >= kL0_SlowdownWritesTrigger && !delayed: + case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed: delayed = true time.Sleep(time.Millisecond) case nn >= n: return false - case v.tLen(0) >= kL0_StopWritesTrigger: + case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger(): delayed = true err = db.compSendIdle(db.tcompCmdC) if err != nil { @@ -109,7 +109,12 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) { for flush() { } if delayed { - db.logf("db@write delayed T·%v", time.Since(start)) + db.writeDelay += time.Since(start) + db.writeDelayN++ + } else if db.writeDelayN > 0 { + db.writeDelay = 0 + db.writeDelayN = 0 + db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay) } return } @@ -120,28 +125,33 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) { // It is safe to modify the contents of the arguments after Write returns. func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) { err = db.ok() - if err != nil || b == nil || b.len() == 0 { + if err != nil || b == nil || b.Len() == 0 { return } b.init(wo.GetSync()) // The write happen synchronously. -retry: select { case db.writeC <- b: if <-db.writeMergedC { return <-db.writeAckC } - goto retry case db.writeLockC <- struct{}{}: + case err = <-db.compPerErrC: + return case _, _ = <-db.closeC: return ErrClosed } merged := 0 + danglingMerge := false defer func() { - <-db.writeLockC + if danglingMerge { + db.writeMergedC <- false + } else { + <-db.writeLockC + } for i := 0; i < merged; i++ { db.writeAckC <- err } @@ -170,7 +180,7 @@ drain: db.writeMergedC <- true merged++ } else { - db.writeMergedC <- false + danglingMerge = true break drain } default: @@ -185,35 +195,43 @@ drain: if b.size() >= (128 << 10) { // Push the write batch to the journal writer select { + case db.journalC <- b: + // Write into memdb + if berr := b.memReplay(mem.mdb); berr != nil { + panic(berr) + } + case err = <-db.compPerErrC: + return case _, _ = <-db.closeC: err = ErrClosed return - case db.journalC <- b: - // Write into memdb - b.memReplay(mem.mdb) } // Wait for journal writer select { - case _, _ = <-db.closeC: - err = ErrClosed - return case err = <-db.journalAckC: if err != nil { // Revert memdb if error detected - b.revertMemReplay(mem.mdb) + if berr := b.revertMemReplay(mem.mdb); berr != nil { + panic(berr) + } return } + case _, _ = <-db.closeC: + err = ErrClosed + return } } else { err = db.writeJournal(b) if err != nil { return } - b.memReplay(mem.mdb) + if berr := b.memReplay(mem.mdb); berr != nil { + panic(berr) + } } // Set last seq number. - db.addSeq(uint64(b.len())) + db.addSeq(uint64(b.Len())) if b.size() >= memFree { db.rotateMem(0) @@ -262,8 +280,11 @@ func (db *DB) CompactRange(r util.Range) error { return err } + // Lock writer. select { case db.writeLockC <- struct{}{}: + case err := <-db.compPerErrC: + return err case _, _ = <-db.closeC: return ErrClosed } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors.go similarity index 50% rename from src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go rename to src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors.go index 8066bd9a..29d0d2f2 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/error.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors.go @@ -7,32 +7,12 @@ package leveldb import ( - "errors" - - "github.com/syndtr/goleveldb/leveldb/util" + "github.com/syndtr/goleveldb/leveldb/errors" ) var ( - ErrNotFound = util.ErrNotFound + ErrNotFound = errors.ErrNotFound ErrSnapshotReleased = errors.New("leveldb: snapshot released") ErrIterReleased = errors.New("leveldb: iterator released") ErrClosed = errors.New("leveldb: closed") ) - -type CorruptionType int - -const ( - CorruptedManifest CorruptionType = iota - MissingFiles -) - -// ErrCorrupted is the type that wraps errors that indicate corruption in -// the database. -type ErrCorrupted struct { - Type CorruptionType - Err error -} - -func (e ErrCorrupted) Error() string { - return e.Err.Error() -} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go new file mode 100644 index 00000000..84b5d6b7 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go @@ -0,0 +1,76 @@ +// Copyright (c) 2014, Suryandaru Triandana +// All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Package errors provides common error types used throughout leveldb. +package errors + +import ( + "errors" + "fmt" + + "github.com/syndtr/goleveldb/leveldb/storage" + "github.com/syndtr/goleveldb/leveldb/util" +) + +var ( + ErrNotFound = New("leveldb: not found") + ErrReleased = util.ErrReleased + ErrHasReleaser = util.ErrHasReleaser +) + +// New returns an error that formats as the given text. +func New(text string) error { + return errors.New(text) +} + +// ErrCorrupted is the type that wraps errors that indicate corruption in +// the database. +type ErrCorrupted struct { + File *storage.FileInfo + Err error +} + +func (e *ErrCorrupted) Error() string { + if e.File != nil { + return fmt.Sprintf("%v [file=%v]", e.Err, e.File) + } else { + return e.Err.Error() + } +} + +// NewErrCorrupted creates new ErrCorrupted error. +func NewErrCorrupted(f storage.File, err error) error { + return &ErrCorrupted{storage.NewFileInfo(f), err} +} + +// IsCorrupted returns a boolean indicating whether the error is indicating +// a corruption. +func IsCorrupted(err error) bool { + switch err.(type) { + case *ErrCorrupted: + return true + } + return false +} + +// ErrMissingFiles is the type that indicating a corruption due to missing +// files. +type ErrMissingFiles struct { + Files []*storage.FileInfo +} + +func (e *ErrMissingFiles) Error() string { return "file missing" } + +// SetFile sets 'file info' of the given error with the given file. +// Currently only ErrCorrupted is supported, otherwise will do nothing. +func SetFile(err error, f storage.File) error { + switch x := err.(type) { + case *ErrCorrupted: + x.File = storage.NewFileInfo(f) + return x + } + return err +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go index cedee793..1ae304ed 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/external_test.go @@ -19,11 +19,12 @@ var _ = testutil.Defer(func() { o := &opt.Options{ BlockCache: opt.NoCache, BlockRestartInterval: 5, - BlockSize: 50, + BlockSize: 80, Compression: opt.NoCompression, CachedOpenFiles: -1, Strict: opt.StrictAll, WriteBuffer: 1000, + CompactionTableSize: 2000, } Describe("write test", func() { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go index 8353b357..939adbb9 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter.go @@ -7,6 +7,7 @@ package iterator import ( + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -22,9 +23,8 @@ type IteratorIndexer interface { type indexedIterator struct { util.BasicReleaser - index IteratorIndexer - strict bool - strictGet bool + index IteratorIndexer + strict bool data Iterator err error @@ -37,11 +37,6 @@ func (i *indexedIterator) setData() { i.data.Release() } i.data = i.index.Get() - if i.strictGet { - if err := i.data.Error(); err != nil { - i.err = err - } - } } func (i *indexedIterator) clearData() { @@ -61,13 +56,11 @@ func (i *indexedIterator) indexErr() { } func (i *indexedIterator) dataErr() bool { - if i.errf != nil { - if err := i.data.Error(); err != nil { + if err := i.data.Error(); err != nil { + if i.errf != nil { i.errf(err) } - } - if i.strict { - if err := i.data.Error(); err != nil { + if i.strict || !errors.IsCorrupted(err) { i.err = err return true } @@ -236,16 +229,14 @@ func (i *indexedIterator) SetErrorCallback(f func(err error)) { i.errf = f } -// NewIndexedIterator returns an indexed iterator. An index is iterator -// that returns another iterator, a data iterator. A data iterator is the +// NewIndexedIterator returns an 'indexed iterator'. An index is iterator +// that returns another iterator, a 'data iterator'. A 'data iterator' is the // iterator that contains actual key/value pairs. // -// If strict is true then error yield by data iterator will halt the indexed -// iterator, on contrary if strict is false then the indexed iterator will -// ignore those error and move on to the next index. If strictGet is true and -// index.Get() yield an 'error iterator' then the indexed iterator will be halted. -// An 'error iterator' is iterator which its Error() method always return non-nil -// even before any 'seeks method' is called. -func NewIndexedIterator(index IteratorIndexer, strict, strictGet bool) Iterator { - return &indexedIterator{index: index, strict: strict, strictGet: strictGet} +// If strict is true the any 'corruption errors' (i.e errors.IsCorrupted(err) == true) +// won't be ignored and will halt 'indexed iterator', otherwise the iterator will +// continue to the next 'data iterator'. Corruption on 'index iterator' will not be +// ignored and will halt the iterator. +func NewIndexedIterator(index IteratorIndexer, strict bool) Iterator { + return &indexedIterator{index: index, strict: strict} } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go index 6a89b383..72a79789 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go @@ -65,7 +65,7 @@ var _ = testutil.Defer(func() { // Test the iterator. t := testutil.IteratorTesting{ KeyValue: kv.Clone(), - Iter: NewIndexedIterator(NewArrayIndexer(index), true, true), + Iter: NewIndexedIterator(NewArrayIndexer(index), true), } testutil.DoIteratorTesting(&t) done <- true diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go index 7ec2fc6f..5ef8d5ba 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go @@ -3,15 +3,9 @@ package iterator_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/syndtr/goleveldb/leveldb/testutil" ) func TestIterator(t *testing.T) { - testutil.RunDefer() - - RegisterFailHandler(Fail) - RunSpecs(t, "Iterator Suite") + testutil.RunSuite(t, "Iterator Suite") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go index 8370e25e..1a7e29df 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter.go @@ -8,6 +8,7 @@ package iterator import ( "github.com/syndtr/goleveldb/leveldb/comparer" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -42,13 +43,11 @@ func assertKey(key []byte) []byte { } func (i *mergedIterator) iterErr(iter Iterator) bool { - if i.errf != nil { - if err := iter.Error(); err != nil { + if err := iter.Error(); err != nil { + if i.errf != nil { i.errf(err) } - } - if i.strict { - if err := iter.Error(); err != nil { + if i.strict || !errors.IsCorrupted(err) { i.err = err return true } @@ -292,9 +291,9 @@ func (i *mergedIterator) SetErrorCallback(f func(err error)) { // keys: if iters[i] contains a key k then iters[j] will not contain that key k. // None of the iters may be nil. // -// If strict is true then error yield by any iterators will halt the merged -// iterator, on contrary if strict is false then the merged iterator will -// ignore those error and move on to the next iterator. +// If strict is true the any 'corruption errors' (i.e errors.IsCorrupted(err) == true) +// won't be ignored and will halt 'merged iterator', otherwise the iterator will +// continue to the next 'input iterator'. func NewMergedIterator(iters []Iterator, cmp comparer.Comparer, strict bool) Iterator { return &mergedIterator{ iters: iters, diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go index e9a19ebc..6519ec66 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go @@ -79,10 +79,10 @@ package journal import ( "encoding/binary" - "errors" "fmt" "io" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -109,7 +109,7 @@ type ErrCorrupted struct { Reason string } -func (e ErrCorrupted) Error() string { +func (e *ErrCorrupted) Error() string { return fmt.Sprintf("leveldb/journal: block/chunk corrupted: %s (%d bytes)", e.Reason, e.Size) } @@ -162,10 +162,10 @@ var errSkip = errors.New("leveldb/journal: skipped") func (r *Reader) corrupt(n int, reason string, skip bool) error { if r.dropper != nil { - r.dropper.Drop(ErrCorrupted{n, reason}) + r.dropper.Drop(&ErrCorrupted{n, reason}) } if r.strict && !skip { - r.err = ErrCorrupted{n, reason} + r.err = errors.NewErrCorrupted(nil, &ErrCorrupted{n, reason}) return r.err } return errSkip diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key.go index b9acf932..98b21f44 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key.go @@ -9,15 +9,30 @@ package leveldb import ( "encoding/binary" "fmt" + + "github.com/syndtr/goleveldb/leveldb/errors" ) -type vType int +type ErrIkeyCorrupted struct { + Ikey []byte + Reason string +} -func (t vType) String() string { - switch t { - case tDel: +func (e *ErrIkeyCorrupted) Error() string { + return fmt.Sprintf("leveldb: iKey %q corrupted: %s", e.Ikey, e.Reason) +} + +func newErrIkeyCorrupted(ikey []byte, reason string) error { + return errors.NewErrCorrupted(nil, &ErrIkeyCorrupted{append([]byte{}, ikey...), reason}) +} + +type kType int + +func (kt kType) String() string { + switch kt { + case ktDel: return "d" - case tVal: + case ktVal: return "v" } return "x" @@ -26,16 +41,16 @@ func (t vType) String() string { // Value types encoded as the last component of internal keys. // Don't modify; this value are saved to disk. const ( - tDel vType = iota - tVal + ktDel kType = iota + ktVal ) -// tSeek defines the vType that should be passed when constructing an +// ktSeek defines the kType that should be passed when constructing an // internal key for seeking to a particular sequence number (since we // sort sequence numbers in decreasing order and the value type is // embedded as the low 8 bits in the sequence number in internal keys, // we need to use the highest-numbered ValueType, not the lowest). -const tSeek = tVal +const ktSeek = ktVal const ( // Maximum value possible for sequence number; the 8-bits are @@ -43,7 +58,7 @@ const ( // 64-bit integer. kMaxSeq uint64 = (uint64(1) << 56) - 1 // Maximum value possible for packed sequence number and type. - kMaxNum uint64 = (kMaxSeq << 8) | uint64(tSeek) + kMaxNum uint64 = (kMaxSeq << 8) | uint64(ktSeek) ) // Maximum number encoded in bytes. @@ -55,85 +70,73 @@ func init() { type iKey []byte -func newIKey(ukey []byte, seq uint64, t vType) iKey { - if seq > kMaxSeq || t > tVal { - panic("invalid seq number or value type") +func newIkey(ukey []byte, seq uint64, kt kType) iKey { + if seq > kMaxSeq { + panic("leveldb: invalid sequence number") + } else if kt > ktVal { + panic("leveldb: invalid type") } - b := make(iKey, len(ukey)+8) - copy(b, ukey) - binary.LittleEndian.PutUint64(b[len(ukey):], (seq<<8)|uint64(t)) - return b + ik := make(iKey, len(ukey)+8) + copy(ik, ukey) + binary.LittleEndian.PutUint64(ik[len(ukey):], (seq<<8)|uint64(kt)) + return ik } -func parseIkey(p []byte) (ukey []byte, seq uint64, t vType, ok bool) { - if len(p) < 8 { - return +func parseIkey(ik []byte) (ukey []byte, seq uint64, kt kType, err error) { + if len(ik) < 8 { + return nil, 0, 0, newErrIkeyCorrupted(ik, "invalid length") } - num := binary.LittleEndian.Uint64(p[len(p)-8:]) - seq, t = uint64(num>>8), vType(num&0xff) - if t > tVal { - return + num := binary.LittleEndian.Uint64(ik[len(ik)-8:]) + seq, kt = uint64(num>>8), kType(num&0xff) + if kt > ktVal { + return nil, 0, 0, newErrIkeyCorrupted(ik, "invalid type") } - ukey = p[:len(p)-8] - ok = true + ukey = ik[:len(ik)-8] return } -func validIkey(p []byte) bool { - _, _, _, ok := parseIkey(p) - return ok +func validIkey(ik []byte) bool { + _, _, _, err := parseIkey(ik) + return err == nil } -func (p iKey) assert() { - if p == nil { - panic("nil iKey") +func (ik iKey) assert() { + if ik == nil { + panic("leveldb: nil iKey") } - if len(p) < 8 { - panic(fmt.Sprintf("invalid iKey %q, len=%d", []byte(p), len(p))) + if len(ik) < 8 { + panic(fmt.Sprintf("leveldb: iKey %q, len=%d: invalid length", ik, len(ik))) } } -func (p iKey) ok() bool { - if len(p) < 8 { - return false - } - _, _, ok := p.parseNum() - return ok +func (ik iKey) ukey() []byte { + ik.assert() + return ik[:len(ik)-8] } -func (p iKey) ukey() []byte { - p.assert() - return p[:len(p)-8] +func (ik iKey) num() uint64 { + ik.assert() + return binary.LittleEndian.Uint64(ik[len(ik)-8:]) } -func (p iKey) num() uint64 { - p.assert() - return binary.LittleEndian.Uint64(p[len(p)-8:]) -} - -func (p iKey) parseNum() (seq uint64, t vType, ok bool) { - if p == nil { - panic("nil iKey") +func (ik iKey) parseNum() (seq uint64, kt kType) { + num := ik.num() + seq, kt = uint64(num>>8), kType(num&0xff) + if kt > ktVal { + panic(fmt.Sprintf("leveldb: iKey %q, len=%d: invalid type %#x", ik, len(ik), kt)) } - if len(p) < 8 { - return - } - num := p.num() - seq, t = uint64(num>>8), vType(num&0xff) - if t > tVal { - return 0, 0, false - } - ok = true return } -func (p iKey) String() string { - if len(p) == 0 { +func (ik iKey) String() string { + if ik == nil { return "" } - if seq, t, ok := p.parseNum(); ok { - return fmt.Sprintf("%s,%s%d", shorten(string(p.ukey())), t, seq) + + if ukey, seq, kt, err := parseIkey(ik); err == nil { + return fmt.Sprintf("%s,%s%d", shorten(string(ukey)), kt, seq) + } else { + return "" } - return "" } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go index e307cfc1..30eadf78 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/key_test.go @@ -15,8 +15,8 @@ import ( var defaultIComparer = &iComparer{comparer.DefaultComparer} -func ikey(key string, seq uint64, t vType) iKey { - return newIKey([]byte(key), uint64(seq), t) +func ikey(key string, seq uint64, kt kType) iKey { + return newIkey([]byte(key), uint64(seq), kt) } func shortSep(a, b []byte) []byte { @@ -37,27 +37,37 @@ func shortSuccessor(b []byte) []byte { return dst } -func testSingleKey(t *testing.T, key string, seq uint64, vt vType) { - ik := ikey(key, seq, vt) +func testSingleKey(t *testing.T, key string, seq uint64, kt kType) { + ik := ikey(key, seq, kt) if !bytes.Equal(ik.ukey(), []byte(key)) { t.Errorf("user key does not equal, got %v, want %v", string(ik.ukey()), key) } - if rseq, rt, ok := ik.parseNum(); ok { + rseq, rt := ik.parseNum() + if rseq != seq { + t.Errorf("seq number does not equal, got %v, want %v", rseq, seq) + } + if rt != kt { + t.Errorf("type does not equal, got %v, want %v", rt, kt) + } + + if rukey, rseq, rt, kerr := parseIkey(ik); kerr == nil { + if !bytes.Equal(rukey, []byte(key)) { + t.Errorf("user key does not equal, got %v, want %v", string(ik.ukey()), key) + } if rseq != seq { t.Errorf("seq number does not equal, got %v, want %v", rseq, seq) } - - if rt != vt { - t.Errorf("type does not equal, got %v, want %v", rt, vt) + if rt != kt { + t.Errorf("type does not equal, got %v, want %v", rt, kt) } } else { - t.Error("cannot parse seq and type") + t.Errorf("key error: %v", kerr) } } -func TestIKey_EncodeDecode(t *testing.T) { +func TestIkey_EncodeDecode(t *testing.T) { keys := []string{"", "k", "hello", "longggggggggggggggggggggg"} seqs := []uint64{ 1, 2, 3, @@ -67,8 +77,8 @@ func TestIKey_EncodeDecode(t *testing.T) { } for _, key := range keys { for _, seq := range seqs { - testSingleKey(t, key, seq, tVal) - testSingleKey(t, "hello", 1, tDel) + testSingleKey(t, key, seq, ktVal) + testSingleKey(t, "hello", 1, ktDel) } } } @@ -79,45 +89,45 @@ func assertBytes(t *testing.T, want, got []byte) { } } -func TestIKeyShortSeparator(t *testing.T) { +func TestIkeyShortSeparator(t *testing.T) { // When user keys are same - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("foo", 99, tVal))) - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("foo", 101, tVal))) - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("foo", 100, tVal))) - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("foo", 100, tDel))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("foo", 99, ktVal))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("foo", 101, ktVal))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("foo", 100, ktVal))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("foo", 100, ktDel))) // When user keys are misordered - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("bar", 99, tVal))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("bar", 99, ktVal))) // When user keys are different, but correctly ordered - assertBytes(t, ikey("g", uint64(kMaxSeq), tSeek), - shortSep(ikey("foo", 100, tVal), - ikey("hello", 200, tVal))) + assertBytes(t, ikey("g", uint64(kMaxSeq), ktSeek), + shortSep(ikey("foo", 100, ktVal), + ikey("hello", 200, ktVal))) // When start user key is prefix of limit user key - assertBytes(t, ikey("foo", 100, tVal), - shortSep(ikey("foo", 100, tVal), - ikey("foobar", 200, tVal))) + assertBytes(t, ikey("foo", 100, ktVal), + shortSep(ikey("foo", 100, ktVal), + ikey("foobar", 200, ktVal))) // When limit user key is prefix of start user key - assertBytes(t, ikey("foobar", 100, tVal), - shortSep(ikey("foobar", 100, tVal), - ikey("foo", 200, tVal))) + assertBytes(t, ikey("foobar", 100, ktVal), + shortSep(ikey("foobar", 100, ktVal), + ikey("foo", 200, ktVal))) } -func TestIKeyShortestSuccessor(t *testing.T) { - assertBytes(t, ikey("g", uint64(kMaxSeq), tSeek), - shortSuccessor(ikey("foo", 100, tVal))) - assertBytes(t, ikey("\xff\xff", 100, tVal), - shortSuccessor(ikey("\xff\xff", 100, tVal))) +func TestIkeyShortestSuccessor(t *testing.T) { + assertBytes(t, ikey("g", uint64(kMaxSeq), ktSeek), + shortSuccessor(ikey("foo", 100, ktVal))) + assertBytes(t, ikey("\xff\xff", 100, ktVal), + shortSuccessor(ikey("\xff\xff", 100, ktVal))) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go index 245b1fd4..fefa007a 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go @@ -3,18 +3,9 @@ package leveldb import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/syndtr/goleveldb/leveldb/testutil" ) -func TestLeveldb(t *testing.T) { - testutil.RunDefer() - - RegisterFailHandler(Fail) - RunSpecs(t, "Leveldb Suite") - - RegisterTestingT(t) - testutil.RunDefer("teardown") +func TestLevelDB(t *testing.T) { + testutil.RunSuite(t, "LevelDB Suite") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go index 83ff7bc6..e5398873 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go @@ -8,17 +8,17 @@ package memdb import ( - "errors" "math/rand" "sync" "github.com/syndtr/goleveldb/leveldb/comparer" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/util" ) var ( - ErrNotFound = util.ErrNotFound + ErrNotFound = errors.ErrNotFound ErrIterReleased = errors.New("leveldb/memdb: iterator released") ) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go index 788539a8..18c304b7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go @@ -3,15 +3,9 @@ package memdb import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/syndtr/goleveldb/leveldb/testutil" ) -func TestMemdb(t *testing.T) { - testutil.RunDefer() - - RegisterFailHandler(Fail) - RunSpecs(t, "Memdb Suite") +func TestMemDB(t *testing.T) { + testutil.RunSuite(t, "MemDB Suite") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go index 2a375ba6..0798b879 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/opt/options.go @@ -11,6 +11,7 @@ import ( "github.com/syndtr/goleveldb/leveldb/cache" "github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/filter" + "math" ) const ( @@ -20,12 +21,24 @@ const ( ) const ( - DefaultBlockCacheSize = 8 * MiB - DefaultBlockRestartInterval = 16 - DefaultBlockSize = 4 * KiB - DefaultCompressionType = SnappyCompression - DefaultCachedOpenFiles = 500 - DefaultWriteBuffer = 4 * MiB + DefaultBlockCacheSize = 8 * MiB + DefaultBlockRestartInterval = 16 + DefaultBlockSize = 4 * KiB + DefaultCompactionExpandLimitFactor = 25 + DefaultCompactionGPOverlapsFactor = 10 + DefaultCompactionL0Trigger = 4 + DefaultCompactionSourceLimitFactor = 1 + DefaultCompactionTableSize = 2 * MiB + DefaultCompactionTableSizeMultiplier = 1.0 + DefaultCompactionTotalSize = 10 * MiB + DefaultCompactionTotalSizeMultiplier = 10.0 + DefaultCompressionType = SnappyCompression + DefaultCachedOpenFiles = 500 + DefaultMaxMemCompationLevel = 2 + DefaultNumLevel = 7 + DefaultWriteBuffer = 4 * MiB + DefaultWriteL0PauseTrigger = 12 + DefaultWriteL0SlowdownTrigger = 8 ) type noCache struct{} @@ -65,34 +78,47 @@ const ( nCompression ) -// Strict is the DB strict level. +// Strict is the DB 'strict level'. type Strict uint const ( // If present then a corrupted or invalid chunk or block in manifest - // journal will cause an error istead of being dropped. + // journal will cause an error instead of being dropped. + // This will prevent database with corrupted manifest to be opened. StrictManifest Strict = 1 << iota - // If present then a corrupted or invalid chunk or block in journal - // will cause an error istead of being dropped. - StrictJournal - // If present then journal chunk checksum will be verified. StrictJournalChecksum - // If present then an invalid key/value pair will cause an error - // instead of being skipped. - StrictIterator + // If present then a corrupted or invalid chunk or block in journal + // will cause an error instead of being dropped. + // This will prevent database with corrupted journal to be opened. + StrictJournal // If present then 'sorted table' block checksum will be verified. + // This has effect on both 'read operation' and compaction. StrictBlockChecksum + // If present then a corrupted 'sorted table' will fails compaction. + // The database will enter read-only mode. + StrictCompaction + + // If present then a corrupted 'sorted table' will halts 'read operation'. + StrictReader + + // If present then leveldb.Recover will drop corrupted 'sorted table'. + StrictRecovery + + // This only applicable for ReadOptions, if present then this ReadOptions + // 'strict level' will override global ones. + StrictOverride + // StrictAll enables all strict flags. - StrictAll = StrictManifest | StrictJournal | StrictJournalChecksum | StrictIterator | StrictBlockChecksum + StrictAll = StrictManifest | StrictJournalChecksum | StrictJournal | StrictBlockChecksum | StrictCompaction | StrictReader | StrictRecovery // DefaultStrict is the default strict flags. Specify any strict flags // will override default strict flags as whole (i.e. not OR'ed). - DefaultStrict = StrictJournalChecksum | StrictBlockChecksum + DefaultStrict = StrictJournalChecksum | StrictBlockChecksum | StrictCompaction | StrictReader // NoStrict disables all strict flags. Override default strict flags. NoStrict = ^StrictAll @@ -110,9 +136,14 @@ type Options struct { // BlockCache provides per-block caching for LevelDB. Specify NoCache to // disable block caching. // - // By default LevelDB will create LRU-cache with capacity of 8MiB. + // By default LevelDB will create LRU-cache with capacity of BlockCacheSize. BlockCache cache.Cache + // BlockCacheSize defines the capacity of the default 'block cache'. + // + // The default value is 8MiB. + BlockCacheSize int + // BlockRestartInterval is the number of keys between restart points for // delta encoding of keys. // @@ -132,6 +163,73 @@ type Options struct { // The default value is 500. CachedOpenFiles int + // CompactionExpandLimitFactor limits compaction size after expanded. + // This will be multiplied by table size limit at compaction target level. + // + // The default value is 25. + CompactionExpandLimitFactor int + + // CompactionGPOverlapsFactor limits overlaps in grandparent (Level + 2) that a + // single 'sorted table' generates. + // This will be multiplied by table size limit at grandparent level. + // + // The default value is 10. + CompactionGPOverlapsFactor int + + // CompactionL0Trigger defines number of 'sorted table' at level-0 that will + // trigger compaction. + // + // The default value is 4. + CompactionL0Trigger int + + // CompactionSourceLimitFactor limits compaction source size. This doesn't apply to + // level-0. + // This will be multiplied by table size limit at compaction target level. + // + // The default value is 1. + CompactionSourceLimitFactor int + + // CompactionTableSize limits size of 'sorted table' that compaction generates. + // The limits for each level will be calculated as: + // CompactionTableSize * (CompactionTableSizeMultiplier ^ Level) + // The multiplier for each level can also fine-tuned using CompactionTableSizeMultiplierPerLevel. + // + // The default value is 2MiB. + CompactionTableSize int + + // CompactionTableSizeMultiplier defines multiplier for CompactionTableSize. + // + // The default value is 1. + CompactionTableSizeMultiplier float64 + + // CompactionTableSizeMultiplierPerLevel defines per-level multiplier for + // CompactionTableSize. + // Use zero to skip a level. + // + // The default value is nil. + CompactionTableSizeMultiplierPerLevel []float64 + + // CompactionTotalSize limits total size of 'sorted table' for each level. + // The limits for each level will be calculated as: + // CompactionTotalSize * (CompactionTotalSizeMultiplier ^ Level) + // The multiplier for each level can also fine-tuned using + // CompactionTotalSizeMultiplierPerLevel. + // + // The default value is 10MiB. + CompactionTotalSize int + + // CompactionTotalSizeMultiplier defines multiplier for CompactionTotalSize. + // + // The default value is 10. + CompactionTotalSizeMultiplier float64 + + // CompactionTotalSizeMultiplierPerLevel defines per-level multiplier for + // CompactionTotalSize. + // Use zero to skip a level. + // + // The default value is nil. + CompactionTotalSizeMultiplierPerLevel []float64 + // Comparer defines a total ordering over the space of []byte keys: a 'less // than' relationship. The same comparison algorithm must be used for reads // and writes over the lifetime of the DB. @@ -144,6 +242,11 @@ type Options struct { // The default value (DefaultCompression) uses snappy compression. Compression Compression + // DisableCompactionBackoff allows disable compaction retry backoff. + // + // The default value is false. + DisableCompactionBackoff bool + // ErrorIfExist defines whether an error should returned if the DB already // exist. // @@ -172,6 +275,19 @@ type Options struct { // The default value is nil. Filter filter.Filter + // MaxMemCompationLevel defines maximum level a newly compacted 'memdb' + // will be pushed into if doesn't creates overlap. This should less than + // NumLevel. Use -1 for level-0. + // + // The default is 2. + MaxMemCompationLevel int + + // NumLevel defines number of database level. The level shouldn't changed + // between opens, or the database will panic. + // + // The default is 7. + NumLevel int + // Strict defines the DB strict level. Strict Strict @@ -183,6 +299,18 @@ type Options struct { // // The default value is 4MiB. WriteBuffer int + + // WriteL0StopTrigger defines number of 'sorted table' at level-0 that will + // pause write. + // + // The default value is 12. + WriteL0PauseTrigger int + + // WriteL0SlowdownTrigger defines number of 'sorted table' at level-0 that + // will trigger write slowdown. + // + // The default value is 8. + WriteL0SlowdownTrigger int } func (o *Options) GetAltFilters() []filter.Filter { @@ -199,6 +327,13 @@ func (o *Options) GetBlockCache() cache.Cache { return o.BlockCache } +func (o *Options) GetBlockCacheSize() int { + if o == nil || o.BlockCacheSize <= 0 { + return DefaultBlockCacheSize + } + return o.BlockCacheSize +} + func (o *Options) GetBlockRestartInterval() int { if o == nil || o.BlockRestartInterval <= 0 { return DefaultBlockRestartInterval @@ -222,6 +357,79 @@ func (o *Options) GetCachedOpenFiles() int { return o.CachedOpenFiles } +func (o *Options) GetCompactionExpandLimit(level int) int { + factor := DefaultCompactionExpandLimitFactor + if o != nil && o.CompactionExpandLimitFactor > 0 { + factor = o.CompactionExpandLimitFactor + } + return o.GetCompactionTableSize(level+1) * factor +} + +func (o *Options) GetCompactionGPOverlaps(level int) int { + factor := DefaultCompactionGPOverlapsFactor + if o != nil && o.CompactionGPOverlapsFactor > 0 { + factor = o.CompactionGPOverlapsFactor + } + return o.GetCompactionTableSize(level+2) * factor +} + +func (o *Options) GetCompactionL0Trigger() int { + if o == nil || o.CompactionL0Trigger == 0 { + return DefaultCompactionL0Trigger + } + return o.CompactionL0Trigger +} + +func (o *Options) GetCompactionSourceLimit(level int) int { + factor := DefaultCompactionSourceLimitFactor + if o != nil && o.CompactionSourceLimitFactor > 0 { + factor = o.CompactionSourceLimitFactor + } + return o.GetCompactionTableSize(level+1) * factor +} + +func (o *Options) GetCompactionTableSize(level int) int { + var ( + base = DefaultCompactionTableSize + mult float64 + ) + if o != nil { + if o.CompactionTableSize > 0 { + base = o.CompactionTableSize + } + if len(o.CompactionTableSizeMultiplierPerLevel) > level && o.CompactionTableSizeMultiplierPerLevel[level] > 0 { + mult = o.CompactionTableSizeMultiplierPerLevel[level] + } else if o.CompactionTableSizeMultiplier > 0 { + mult = math.Pow(o.CompactionTableSizeMultiplier, float64(level)) + } + } + if mult == 0 { + mult = math.Pow(DefaultCompactionTableSizeMultiplier, float64(level)) + } + return int(float64(base) * mult) +} + +func (o *Options) GetCompactionTotalSize(level int) int64 { + var ( + base = DefaultCompactionTotalSize + mult float64 + ) + if o != nil { + if o.CompactionTotalSize > 0 { + base = o.CompactionTotalSize + } + if len(o.CompactionTotalSizeMultiplierPerLevel) > level && o.CompactionTotalSizeMultiplierPerLevel[level] > 0 { + mult = o.CompactionTotalSizeMultiplierPerLevel[level] + } else if o.CompactionTotalSizeMultiplier > 0 { + mult = math.Pow(o.CompactionTotalSizeMultiplier, float64(level)) + } + } + if mult == 0 { + mult = math.Pow(DefaultCompactionTotalSizeMultiplier, float64(level)) + } + return int64(float64(base) * mult) +} + func (o *Options) GetComparer() comparer.Comparer { if o == nil || o.Comparer == nil { return comparer.DefaultComparer @@ -236,6 +444,13 @@ func (o *Options) GetCompression() Compression { return o.Compression } +func (o *Options) GetDisableCompactionBackoff() bool { + if o == nil { + return false + } + return o.DisableCompactionBackoff +} + func (o *Options) GetErrorIfExist() bool { if o == nil { return false @@ -257,6 +472,28 @@ func (o *Options) GetFilter() filter.Filter { return o.Filter } +func (o *Options) GetMaxMemCompationLevel() int { + level := DefaultMaxMemCompationLevel + if o != nil { + if o.MaxMemCompationLevel > 0 { + level = o.MaxMemCompationLevel + } else if o.MaxMemCompationLevel == -1 { + level = 0 + } + } + if level >= o.GetNumLevel() { + return o.GetNumLevel() - 1 + } + return level +} + +func (o *Options) GetNumLevel() int { + if o == nil || o.NumLevel <= 0 { + return DefaultNumLevel + } + return o.NumLevel +} + func (o *Options) GetStrict(strict Strict) bool { if o == nil || o.Strict == 0 { return DefaultStrict&strict != 0 @@ -271,6 +508,20 @@ func (o *Options) GetWriteBuffer() int { return o.WriteBuffer } +func (o *Options) GetWriteL0PauseTrigger() int { + if o == nil || o.WriteL0PauseTrigger == 0 { + return DefaultWriteL0PauseTrigger + } + return o.WriteL0PauseTrigger +} + +func (o *Options) GetWriteL0SlowdownTrigger() int { + if o == nil || o.WriteL0SlowdownTrigger == 0 { + return DefaultWriteL0SlowdownTrigger + } + return o.WriteL0SlowdownTrigger +} + // ReadOptions holds the optional parameters for 'read operation'. The // 'read operation' includes Get, Find and NewIterator. type ReadOptions struct { @@ -281,8 +532,8 @@ type ReadOptions struct { // The default value is false. DontFillCache bool - // Strict overrides global DB strict level. Only StrictIterator and - // StrictBlockChecksum that does have effects here. + // Strict will be OR'ed with global DB 'strict level' unless StrictOverride + // is present. Currently only StrictReader that has effect here. Strict Strict } @@ -324,3 +575,11 @@ func (wo *WriteOptions) GetSync() bool { } return wo.Sync } + +func GetStrict(o *Options, ro *ReadOptions, strict Strict) bool { + if ro.GetStrict(StrictOverride) { + return ro.GetStrict(strict) + } else { + return o.GetStrict(strict) || ro.GetStrict(strict) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go index fc6a9696..3928cd16 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/options.go @@ -12,30 +12,89 @@ import ( "github.com/syndtr/goleveldb/leveldb/opt" ) -func (s *session) setOptions(o *opt.Options) { - s.o = &opt.Options{} +func dupOptions(o *opt.Options) *opt.Options { + newo := &opt.Options{} if o != nil { - *s.o = *o + *newo = *o } + if newo.Strict == 0 { + newo.Strict = opt.DefaultStrict + } + return newo +} + +func (s *session) setOptions(o *opt.Options) { + no := dupOptions(o) // Alternative filters. if filters := o.GetAltFilters(); len(filters) > 0 { - s.o.AltFilters = make([]filter.Filter, len(filters)) + no.AltFilters = make([]filter.Filter, len(filters)) for i, filter := range filters { - s.o.AltFilters[i] = &iFilter{filter} + no.AltFilters[i] = &iFilter{filter} } } // Block cache. switch o.GetBlockCache() { case nil: - s.o.BlockCache = cache.NewLRUCache(opt.DefaultBlockCacheSize) + no.BlockCache = cache.NewLRUCache(o.GetBlockCacheSize()) case opt.NoCache: - s.o.BlockCache = nil + no.BlockCache = nil } // Comparer. s.icmp = &iComparer{o.GetComparer()} - s.o.Comparer = s.icmp + no.Comparer = s.icmp // Filter. if filter := o.GetFilter(); filter != nil { - s.o.Filter = &iFilter{filter} + no.Filter = &iFilter{filter} + } + + s.o = &cachedOptions{Options: no} + s.o.cache() +} + +type cachedOptions struct { + *opt.Options + + compactionExpandLimit []int + compactionGPOverlaps []int + compactionSourceLimit []int + compactionTableSize []int + compactionTotalSize []int64 +} + +func (co *cachedOptions) cache() { + numLevel := co.Options.GetNumLevel() + + co.compactionExpandLimit = make([]int, numLevel) + co.compactionGPOverlaps = make([]int, numLevel) + co.compactionSourceLimit = make([]int, numLevel) + co.compactionTableSize = make([]int, numLevel) + co.compactionTotalSize = make([]int64, numLevel) + + for level := 0; level < numLevel; level++ { + co.compactionExpandLimit[level] = co.Options.GetCompactionExpandLimit(level) + co.compactionGPOverlaps[level] = co.Options.GetCompactionGPOverlaps(level) + co.compactionSourceLimit[level] = co.Options.GetCompactionSourceLimit(level) + co.compactionTableSize[level] = co.Options.GetCompactionTableSize(level) + co.compactionTotalSize[level] = co.Options.GetCompactionTotalSize(level) } } + +func (co *cachedOptions) GetCompactionExpandLimit(level int) int { + return co.compactionExpandLimit[level] +} + +func (co *cachedOptions) GetCompactionGPOverlaps(level int) int { + return co.compactionGPOverlaps[level] +} + +func (co *cachedOptions) GetCompactionSourceLimit(level int) int { + return co.compactionSourceLimit[level] +} + +func (co *cachedOptions) GetCompactionTableSize(level int) int { + return co.compactionTableSize[level] +} + +func (co *cachedOptions) GetCompactionTotalSize(level int) int64 { + return co.compactionTotalSize[level] +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go index 7fc08e7d..5b251efb 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session.go @@ -7,12 +7,13 @@ package leveldb import ( - "errors" + "fmt" "io" "os" "sync" "sync/atomic" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/journal" "github.com/syndtr/goleveldb/leveldb/opt" @@ -20,18 +21,31 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) +type ErrManifestCorrupted struct { + Field string + Reason string +} + +func (e *ErrManifestCorrupted) Error() string { + return fmt.Sprintf("leveldb: manifest corrupted (field '%s'): %s", e.Field, e.Reason) +} + +func newErrManifestCorrupted(f storage.File, field, reason string) error { + return errors.NewErrCorrupted(f, &ErrManifestCorrupted{field, reason}) +} + // session represent a persistent database session. type session struct { // Need 64-bit alignment. - stFileNum uint64 // current unused file number + stNextFileNum uint64 // current unused file number stJournalNum uint64 // current journal file number; need external synchronization stPrevJournalNum uint64 // prev journal file number; no longer used; for compatibility with older version of leveldb - stSeq uint64 // last mem compacted seq; need external synchronization + stSeqNum uint64 // last mem compacted seq; need external synchronization stTempFileNum uint64 stor storage.Storage storLock util.Releaser - o *opt.Options + o *cachedOptions icmp *iComparer tops *tOps @@ -39,9 +53,9 @@ type session struct { manifestWriter storage.Writer manifestFile storage.File - stCptrs [kNumLevels]iKey // compact pointers; need external synchronization - stVersion *version // current version - vmu sync.Mutex + stCompPtrs []iKey // compaction pointers; need external synchronization + stVersion *version // current version + vmu sync.Mutex } // Creates new initialized session instance. @@ -54,13 +68,14 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) { return } s = &session{ - stor: stor, - storLock: storLock, + stor: stor, + storLock: storLock, + stCompPtrs: make([]iKey, o.GetNumLevel()), } s.setOptions(o) s.tops = newTableOps(s, s.o.GetCachedOpenFiles()) - s.setVersion(&version{s: s}) - s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock D·DeletedEntry L·Level Q·SeqNum T·TimeElapsed") + s.setVersion(newVersion(s)) + s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed") return } @@ -100,26 +115,26 @@ func (s *session) recover() (err error) { // Don't return os.ErrNotExist if the underlying storage contains // other files that belong to LevelDB. So the DB won't get trashed. if files, _ := s.stor.GetFiles(storage.TypeAll); len(files) > 0 { - err = ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest file missing")} + err = &errors.ErrCorrupted{File: &storage.FileInfo{Type: storage.TypeManifest}, Err: &errors.ErrMissingFiles{}} } } }() - file, err := s.stor.GetManifest() + m, err := s.stor.GetManifest() if err != nil { return } - reader, err := file.Open() + reader, err := m.Open() if err != nil { return } defer reader.Close() strict := s.o.GetStrict(opt.StrictManifest) - jr := journal.NewReader(reader, dropper{s, file}, strict, true) + jr := journal.NewReader(reader, dropper{s, m}, strict, true) - staging := s.version_NB().newStaging() - rec := &sessionRecord{} + staging := s.stVersion.newStaging() + rec := &sessionRecord{numLevel: s.o.GetNumLevel()} for { var r io.Reader r, err = jr.Next() @@ -128,51 +143,57 @@ func (s *session) recover() (err error) { err = nil break } - return + return errors.SetFile(err, m) } err = rec.decode(r) if err == nil { // save compact pointers - for _, r := range rec.compactionPointers { - s.stCptrs[r.level] = iKey(r.ikey) + for _, r := range rec.compPtrs { + s.stCompPtrs[r.level] = iKey(r.ikey) } // commit record to version staging staging.commit(rec) - } else if strict { - return ErrCorrupted{Type: CorruptedManifest, Err: err} } else { - s.logf("manifest error: %v (skipped)", err) + err = errors.SetFile(err, m) + if strict || !errors.IsCorrupted(err) { + return + } else { + s.logf("manifest error: %v (skipped)", errors.SetFile(err, m)) + } } - rec.resetCompactionPointers() + rec.resetCompPtrs() rec.resetAddedTables() rec.resetDeletedTables() } switch { case !rec.has(recComparer): - return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing comparer name")} + return newErrManifestCorrupted(m, "comparer", "missing") case rec.comparer != s.icmp.uName(): - return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: comparer mismatch, " + "want '" + s.icmp.uName() + "', " + "got '" + rec.comparer + "'")} - case !rec.has(recNextNum): - return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing next file number")} + return newErrManifestCorrupted(m, "comparer", fmt.Sprintf("mismatch: want '%s', got '%s'", s.icmp.uName(), rec.comparer)) + case !rec.has(recNextFileNum): + return newErrManifestCorrupted(m, "next-file-num", "missing") case !rec.has(recJournalNum): - return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing journal file number")} - case !rec.has(recSeq): - return ErrCorrupted{Type: CorruptedManifest, Err: errors.New("leveldb: manifest missing seq number")} + return newErrManifestCorrupted(m, "journal-file-num", "missing") + case !rec.has(recSeqNum): + return newErrManifestCorrupted(m, "seq-num", "missing") } - s.manifestFile = file + s.manifestFile = m s.setVersion(staging.finish()) - s.setFileNum(rec.nextNum) + s.setNextFileNum(rec.nextFileNum) s.recordCommited(rec) return nil } // Commit session; need external synchronization. func (s *session) commit(r *sessionRecord) (err error) { + v := s.version() + defer v.release() + // spawn new version based on current version - nv := s.version_NB().spawn(r) + nv := v.spawn(r) if s.manifest == nil { // manifest journal writer not yet created, create one @@ -191,13 +212,13 @@ func (s *session) commit(r *sessionRecord) (err error) { // Pick a compaction based on current state; need external synchronization. func (s *session) pickCompaction() *compaction { - v := s.version_NB() + v := s.version() var level int var t0 tFiles if v.cScore >= 1 { level = v.cLevel - cptr := s.stCptrs[level] + cptr := s.stCompPtrs[level] tables := v.tables[level] for _, t := range tables { if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 { @@ -214,27 +235,21 @@ func (s *session) pickCompaction() *compaction { level = ts.level t0 = append(t0, ts.table) } else { + v.release() return nil } } - c := &compaction{s: s, v: v, level: level} - if level == 0 { - imin, imax := t0.getRange(s.icmp) - t0 = v.tables[0].getOverlaps(t0[:0], s.icmp, imin.ukey(), imax.ukey(), true) - } - - c.tables[0] = t0 - c.expand() - return c + return newCompaction(s, v, level, t0) } // Create compaction from given level and range; need external synchronization. func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction { - v := s.version_NB() + v := s.version() t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0) if len(t0) == 0 { + v.release() return nil } @@ -243,7 +258,7 @@ func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction { // and we must not pick one file and drop another older file if the // two files overlap. if level > 0 { - limit := uint64(kMaxTableSize) + limit := uint64(v.s.o.GetCompactionSourceLimit(level)) total := uint64(0) for i, t := range t0 { total += t.size @@ -255,9 +270,20 @@ func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction { } } - c := &compaction{s: s, v: v, level: level} - c.tables[0] = t0 + return newCompaction(s, v, level, t0) +} + +func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction { + c := &compaction{ + s: s, + v: v, + level: level, + tables: [2]tFiles{t0, nil}, + maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)), + tPtrs: make([]int, s.o.GetNumLevel()), + } c.expand() + c.save() return c } @@ -266,25 +292,57 @@ type compaction struct { s *session v *version - level int - tables [2]tFiles + level int + tables [2]tFiles + maxGPOverlaps uint64 - gp tFiles - gpidx int - seenKey bool - overlappedBytes uint64 - imin, imax iKey + gp tFiles + gpi int + seenKey bool + gpOverlappedBytes uint64 + imin, imax iKey + tPtrs []int + released bool - tPtrs [kNumLevels]int + snapGPI int + snapSeenKey bool + snapGPOverlappedBytes uint64 + snapTPtrs []int +} + +func (c *compaction) save() { + c.snapGPI = c.gpi + c.snapSeenKey = c.seenKey + c.snapGPOverlappedBytes = c.gpOverlappedBytes + c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...) +} + +func (c *compaction) restore() { + c.gpi = c.snapGPI + c.seenKey = c.snapSeenKey + c.gpOverlappedBytes = c.snapGPOverlappedBytes + c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...) +} + +func (c *compaction) release() { + if !c.released { + c.released = true + c.v.release() + } } // Expand compacted tables; need external synchronization. func (c *compaction) expand() { - level := c.level - vt0, vt1 := c.v.tables[level], c.v.tables[level+1] + limit := uint64(c.s.o.GetCompactionExpandLimit(c.level)) + vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1] t0, t1 := c.tables[0], c.tables[1] imin, imax := t0.getRange(c.s.icmp) + // We expand t0 here just incase ukey hop across tables. + t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0) + if len(t0) != len(c.tables[0]) { + imin, imax = t0.getRange(c.s.icmp) + } t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false) // Get entire range covered by compaction. amin, amax := append(t0, t1...).getRange(c.s.icmp) @@ -292,13 +350,13 @@ func (c *compaction) expand() { // See if we can grow the number of inputs in "level" without // changing the number of "level+1" files we pick up. if len(t1) > 0 { - exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), level == 0) - if len(exp0) > len(t0) && t1.size()+exp0.size() < kExpCompactionMaxBytes { + exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0) + if len(exp0) > len(t0) && t1.size()+exp0.size() < limit { xmin, xmax := exp0.getRange(c.s.icmp) exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false) if len(exp1) == len(t1) { c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)", - level, level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())), + c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())), len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size()))) imin, imax = xmin, xmax t0, t1 = exp0, exp1 @@ -309,8 +367,8 @@ func (c *compaction) expand() { // Compute the set of grandparent files that overlap this compaction // (parent == level+1; grandparent == level+2) - if level+2 < kNumLevels { - c.gp = c.v.tables[level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false) + if c.level+2 < c.s.o.GetNumLevel() { + c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false) } c.tables[0], c.tables[1] = t0, t1 @@ -319,7 +377,7 @@ func (c *compaction) expand() { // Check whether compaction is trivial. func (c *compaction) trivial() bool { - return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= kMaxGrandParentOverlapBytes + return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps } func (c *compaction) baseLevelForKey(ukey []byte) bool { @@ -341,20 +399,20 @@ func (c *compaction) baseLevelForKey(ukey []byte) bool { } func (c *compaction) shouldStopBefore(ikey iKey) bool { - for ; c.gpidx < len(c.gp); c.gpidx++ { - gp := c.gp[c.gpidx] + for ; c.gpi < len(c.gp); c.gpi++ { + gp := c.gp[c.gpi] if c.s.icmp.Compare(ikey, gp.imax) <= 0 { break } if c.seenKey { - c.overlappedBytes += gp.size + c.gpOverlappedBytes += gp.size } } c.seenKey = true - if c.overlappedBytes > kMaxGrandParentOverlapBytes { + if c.gpOverlappedBytes > c.maxGPOverlaps { // Too much overlap for current output; start new output. - c.overlappedBytes = 0 + c.gpOverlappedBytes = 0 return true } return false @@ -373,8 +431,12 @@ func (c *compaction) newIterator() iterator.Iterator { // Options. ro := &opt.ReadOptions{ DontFillCache: true, + Strict: opt.StrictOverride, + } + strict := c.s.o.GetStrict(opt.StrictCompaction) + if strict { + ro.Strict |= opt.StrictReader } - strict := c.s.o.GetStrict(opt.StrictIterator) for i, tables := range c.tables { if len(tables) == 0 { @@ -387,10 +449,10 @@ func (c *compaction) newIterator() iterator.Iterator { its = append(its, c.s.tops.newIterator(t, nil, ro)) } } else { - it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict, true) + it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict) its = append(its, it) } } - return iterator.NewMergedIterator(its, c.s.icmp, true) + return iterator.NewMergedIterator(its, c.s.icmp, strict) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go index 27212958..1bdcc68f 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record.go @@ -9,11 +9,11 @@ package leveldb import ( "bufio" "encoding/binary" - "errors" "io" -) + "strings" -var errCorruptManifest = errors.New("leveldb: corrupt manifest") + "github.com/syndtr/goleveldb/leveldb/errors" +) type byteReader interface { io.Reader @@ -22,13 +22,13 @@ type byteReader interface { // These numbers are written to disk and should not be changed. const ( - recComparer = 1 - recJournalNum = 2 - recNextNum = 3 - recSeq = 4 - recCompactionPointer = 5 - recDeletedTable = 6 - recNewTable = 7 + recComparer = 1 + recJournalNum = 2 + recNextFileNum = 3 + recSeqNum = 4 + recCompPtr = 5 + recDelTable = 6 + recAddTable = 7 // 8 was used for large value refs recPrevJournalNum = 9 ) @@ -38,7 +38,7 @@ type cpRecord struct { ikey iKey } -type ntRecord struct { +type atRecord struct { level int num uint64 size uint64 @@ -46,27 +46,26 @@ type ntRecord struct { imax iKey } -func (r ntRecord) makeFile(s *session) *tFile { - return newTableFile(s.getTableFile(r.num), r.size, r.imin, r.imax) -} - type dtRecord struct { level int num uint64 } type sessionRecord struct { - hasRec int - comparer string - journalNum uint64 - prevJournalNum uint64 - nextNum uint64 - seq uint64 - compactionPointers []cpRecord - addedTables []ntRecord - deletedTables []dtRecord - scratch [binary.MaxVarintLen64]byte - err error + numLevel int + + hasRec int + comparer string + journalNum uint64 + prevJournalNum uint64 + nextFileNum uint64 + seqNum uint64 + compPtrs []cpRecord + addedTables []atRecord + deletedTables []dtRecord + + scratch [binary.MaxVarintLen64]byte + err error } func (p *sessionRecord) has(rec int) bool { @@ -88,29 +87,29 @@ func (p *sessionRecord) setPrevJournalNum(num uint64) { p.prevJournalNum = num } -func (p *sessionRecord) setNextNum(num uint64) { - p.hasRec |= 1 << recNextNum - p.nextNum = num +func (p *sessionRecord) setNextFileNum(num uint64) { + p.hasRec |= 1 << recNextFileNum + p.nextFileNum = num } -func (p *sessionRecord) setSeq(seq uint64) { - p.hasRec |= 1 << recSeq - p.seq = seq +func (p *sessionRecord) setSeqNum(num uint64) { + p.hasRec |= 1 << recSeqNum + p.seqNum = num } -func (p *sessionRecord) addCompactionPointer(level int, ikey iKey) { - p.hasRec |= 1 << recCompactionPointer - p.compactionPointers = append(p.compactionPointers, cpRecord{level, ikey}) +func (p *sessionRecord) addCompPtr(level int, ikey iKey) { + p.hasRec |= 1 << recCompPtr + p.compPtrs = append(p.compPtrs, cpRecord{level, ikey}) } -func (p *sessionRecord) resetCompactionPointers() { - p.hasRec &= ^(1 << recCompactionPointer) - p.compactionPointers = p.compactionPointers[:0] +func (p *sessionRecord) resetCompPtrs() { + p.hasRec &= ^(1 << recCompPtr) + p.compPtrs = p.compPtrs[:0] } func (p *sessionRecord) addTable(level int, num, size uint64, imin, imax iKey) { - p.hasRec |= 1 << recNewTable - p.addedTables = append(p.addedTables, ntRecord{level, num, size, imin, imax}) + p.hasRec |= 1 << recAddTable + p.addedTables = append(p.addedTables, atRecord{level, num, size, imin, imax}) } func (p *sessionRecord) addTableFile(level int, t *tFile) { @@ -118,17 +117,17 @@ func (p *sessionRecord) addTableFile(level int, t *tFile) { } func (p *sessionRecord) resetAddedTables() { - p.hasRec &= ^(1 << recNewTable) + p.hasRec &= ^(1 << recAddTable) p.addedTables = p.addedTables[:0] } -func (p *sessionRecord) deleteTable(level int, num uint64) { - p.hasRec |= 1 << recDeletedTable +func (p *sessionRecord) delTable(level int, num uint64) { + p.hasRec |= 1 << recDelTable p.deletedTables = append(p.deletedTables, dtRecord{level, num}) } func (p *sessionRecord) resetDeletedTables() { - p.hasRec &= ^(1 << recDeletedTable) + p.hasRec &= ^(1 << recDelTable) p.deletedTables = p.deletedTables[:0] } @@ -161,26 +160,26 @@ func (p *sessionRecord) encode(w io.Writer) error { p.putUvarint(w, recJournalNum) p.putUvarint(w, p.journalNum) } - if p.has(recNextNum) { - p.putUvarint(w, recNextNum) - p.putUvarint(w, p.nextNum) + if p.has(recNextFileNum) { + p.putUvarint(w, recNextFileNum) + p.putUvarint(w, p.nextFileNum) } - if p.has(recSeq) { - p.putUvarint(w, recSeq) - p.putUvarint(w, p.seq) + if p.has(recSeqNum) { + p.putUvarint(w, recSeqNum) + p.putUvarint(w, p.seqNum) } - for _, r := range p.compactionPointers { - p.putUvarint(w, recCompactionPointer) + for _, r := range p.compPtrs { + p.putUvarint(w, recCompPtr) p.putUvarint(w, uint64(r.level)) p.putBytes(w, r.ikey) } for _, r := range p.deletedTables { - p.putUvarint(w, recDeletedTable) + p.putUvarint(w, recDelTable) p.putUvarint(w, uint64(r.level)) p.putUvarint(w, r.num) } for _, r := range p.addedTables { - p.putUvarint(w, recNewTable) + p.putUvarint(w, recAddTable) p.putUvarint(w, uint64(r.level)) p.putUvarint(w, r.num) p.putUvarint(w, r.size) @@ -190,14 +189,16 @@ func (p *sessionRecord) encode(w io.Writer) error { return p.err } -func (p *sessionRecord) readUvarint(r io.ByteReader) uint64 { +func (p *sessionRecord) readUvarintMayEOF(field string, r io.ByteReader, mayEOF bool) uint64 { if p.err != nil { return 0 } x, err := binary.ReadUvarint(r) if err != nil { - if err == io.EOF { - p.err = errCorruptManifest + if err == io.ErrUnexpectedEOF || (mayEOF == false && err == io.EOF) { + p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "short read"}) + } else if strings.HasPrefix(err.Error(), "binary:") { + p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, err.Error()}) } else { p.err = err } @@ -206,35 +207,39 @@ func (p *sessionRecord) readUvarint(r io.ByteReader) uint64 { return x } -func (p *sessionRecord) readBytes(r byteReader) []byte { +func (p *sessionRecord) readUvarint(field string, r io.ByteReader) uint64 { + return p.readUvarintMayEOF(field, r, false) +} + +func (p *sessionRecord) readBytes(field string, r byteReader) []byte { if p.err != nil { return nil } - n := p.readUvarint(r) + n := p.readUvarint(field, r) if p.err != nil { return nil } x := make([]byte, n) _, p.err = io.ReadFull(r, x) if p.err != nil { - if p.err == io.EOF { - p.err = errCorruptManifest + if p.err == io.ErrUnexpectedEOF { + p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "short read"}) } return nil } return x } -func (p *sessionRecord) readLevel(r io.ByteReader) int { +func (p *sessionRecord) readLevel(field string, r io.ByteReader) int { if p.err != nil { return 0 } - x := p.readUvarint(r) + x := p.readUvarint(field, r) if p.err != nil { return 0 } - if x >= kNumLevels { - p.err = errCorruptManifest + if x >= uint64(p.numLevel) { + p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "invalid level number"}) return 0 } return int(x) @@ -247,59 +252,59 @@ func (p *sessionRecord) decode(r io.Reader) error { } p.err = nil for p.err == nil { - rec, err := binary.ReadUvarint(br) - if err != nil { - if err == io.EOF { - err = nil + rec := p.readUvarintMayEOF("field-header", br, true) + if p.err != nil { + if p.err == io.EOF { + return nil } - return err + return p.err } switch rec { case recComparer: - x := p.readBytes(br) + x := p.readBytes("comparer", br) if p.err == nil { p.setComparer(string(x)) } case recJournalNum: - x := p.readUvarint(br) + x := p.readUvarint("journal-num", br) if p.err == nil { p.setJournalNum(x) } case recPrevJournalNum: - x := p.readUvarint(br) + x := p.readUvarint("prev-journal-num", br) if p.err == nil { p.setPrevJournalNum(x) } - case recNextNum: - x := p.readUvarint(br) + case recNextFileNum: + x := p.readUvarint("next-file-num", br) if p.err == nil { - p.setNextNum(x) + p.setNextFileNum(x) } - case recSeq: - x := p.readUvarint(br) + case recSeqNum: + x := p.readUvarint("seq-num", br) if p.err == nil { - p.setSeq(x) + p.setSeqNum(x) } - case recCompactionPointer: - level := p.readLevel(br) - ikey := p.readBytes(br) + case recCompPtr: + level := p.readLevel("comp-ptr.level", br) + ikey := p.readBytes("comp-ptr.ikey", br) if p.err == nil { - p.addCompactionPointer(level, iKey(ikey)) + p.addCompPtr(level, iKey(ikey)) } - case recNewTable: - level := p.readLevel(br) - num := p.readUvarint(br) - size := p.readUvarint(br) - imin := p.readBytes(br) - imax := p.readBytes(br) + case recAddTable: + level := p.readLevel("add-table.level", br) + num := p.readUvarint("add-table.num", br) + size := p.readUvarint("add-table.size", br) + imin := p.readBytes("add-table.imin", br) + imax := p.readBytes("add-table.imax", br) if p.err == nil { p.addTable(level, num, size, imin, imax) } - case recDeletedTable: - level := p.readLevel(br) - num := p.readUvarint(br) + case recDelTable: + level := p.readLevel("del-table.level", br) + num := p.readUvarint("del-table.num", br) if p.err == nil { - p.deleteTable(level, num) + p.delTable(level, num) } } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go index 029fabfe..c0c035ae 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go @@ -9,6 +9,8 @@ package leveldb import ( "bytes" "testing" + + "github.com/syndtr/goleveldb/leveldb/opt" ) func decodeEncode(v *sessionRecord) (res bool, err error) { @@ -17,7 +19,7 @@ func decodeEncode(v *sessionRecord) (res bool, err error) { if err != nil { return } - v2 := new(sessionRecord) + v2 := &sessionRecord{numLevel: opt.DefaultNumLevel} err = v.decode(b) if err != nil { return @@ -32,7 +34,7 @@ func decodeEncode(v *sessionRecord) (res bool, err error) { func TestSessionRecord_EncodeDecode(t *testing.T) { big := uint64(1) << 50 - v := new(sessionRecord) + v := &sessionRecord{numLevel: opt.DefaultNumLevel} i := uint64(0) test := func() { res, err := decodeEncode(v) @@ -47,16 +49,16 @@ func TestSessionRecord_EncodeDecode(t *testing.T) { for ; i < 4; i++ { test() v.addTable(3, big+300+i, big+400+i, - newIKey([]byte("foo"), big+500+1, tVal), - newIKey([]byte("zoo"), big+600+1, tDel)) - v.deleteTable(4, big+700+i) - v.addCompactionPointer(int(i), newIKey([]byte("x"), big+900+1, tVal)) + newIkey([]byte("foo"), big+500+1, ktVal), + newIkey([]byte("zoo"), big+600+1, ktDel)) + v.delTable(4, big+700+i) + v.addCompPtr(int(i), newIkey([]byte("x"), big+900+1, ktVal)) } v.setComparer("foo") v.setJournalNum(big + 100) v.setPrevJournalNum(big + 99) - v.setNextNum(big + 200) - v.setSeq(big + 1000) + v.setNextFileNum(big + 200) + v.setSeqNum(big + 1000) test() } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go index 715c9f5b..007c02cd 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/session_util.go @@ -22,7 +22,7 @@ type dropper struct { } func (d dropper) Drop(err error) { - if e, ok := err.(journal.ErrCorrupted); ok { + if e, ok := err.(*journal.ErrCorrupted); ok { d.s.logf("journal@drop %s-%d S·%s %q", d.file.Type(), d.file.Num(), shortenb(e.Size), e.Reason) } else { d.s.logf("journal@drop %s-%d %q", d.file.Type(), d.file.Num(), err) @@ -51,9 +51,14 @@ func (s *session) newTemp() storage.File { return s.stor.GetFile(num, storage.TypeTemp) } +func (s *session) tableFileFromRecord(r atRecord) *tFile { + return newTableFile(s.getTableFile(r.num), r.size, r.imin, r.imax) +} + // Session state. -// Get current version. +// Get current version. This will incr version ref, must call +// version.release (exactly once) after use. func (s *session) version() *version { s.vmu.Lock() defer s.vmu.Unlock() @@ -61,61 +66,56 @@ func (s *session) version() *version { return s.stVersion } -// Get current version; no barrier. -func (s *session) version_NB() *version { - return s.stVersion -} - // Set current version to v. func (s *session) setVersion(v *version) { s.vmu.Lock() - v.ref = 1 + v.ref = 1 // Holds by session. if old := s.stVersion; old != nil { - v.ref++ + v.ref++ // Holds by old version. old.next = v - old.release_NB() + old.releaseNB() } s.stVersion = v s.vmu.Unlock() } // Get current unused file number. -func (s *session) fileNum() uint64 { - return atomic.LoadUint64(&s.stFileNum) +func (s *session) nextFileNum() uint64 { + return atomic.LoadUint64(&s.stNextFileNum) } -// Get current unused file number to num. -func (s *session) setFileNum(num uint64) { - atomic.StoreUint64(&s.stFileNum, num) +// Set current unused file number to num. +func (s *session) setNextFileNum(num uint64) { + atomic.StoreUint64(&s.stNextFileNum, num) } // Mark file number as used. func (s *session) markFileNum(num uint64) { - num += 1 + nextFileNum := num + 1 for { - old, x := s.stFileNum, num + old, x := s.stNextFileNum, nextFileNum if old > x { x = old } - if atomic.CompareAndSwapUint64(&s.stFileNum, old, x) { + if atomic.CompareAndSwapUint64(&s.stNextFileNum, old, x) { break } } } // Allocate a file number. -func (s *session) allocFileNum() (num uint64) { - return atomic.AddUint64(&s.stFileNum, 1) - 1 +func (s *session) allocFileNum() uint64 { + return atomic.AddUint64(&s.stNextFileNum, 1) - 1 } // Reuse given file number. func (s *session) reuseFileNum(num uint64) { for { - old, x := s.stFileNum, num + old, x := s.stNextFileNum, num if old != x+1 { x = old } - if atomic.CompareAndSwapUint64(&s.stFileNum, old, x) { + if atomic.CompareAndSwapUint64(&s.stNextFileNum, old, x) { break } } @@ -126,20 +126,20 @@ func (s *session) reuseFileNum(num uint64) { // Fill given session record obj with current states; need external // synchronization. func (s *session) fillRecord(r *sessionRecord, snapshot bool) { - r.setNextNum(s.fileNum()) + r.setNextFileNum(s.nextFileNum()) if snapshot { if !r.has(recJournalNum) { r.setJournalNum(s.stJournalNum) } - if !r.has(recSeq) { - r.setSeq(s.stSeq) + if !r.has(recSeqNum) { + r.setSeqNum(s.stSeqNum) } - for level, ik := range s.stCptrs { + for level, ik := range s.stCompPtrs { if ik != nil { - r.addCompactionPointer(level, ik) + r.addCompPtr(level, ik) } } @@ -158,12 +158,12 @@ func (s *session) recordCommited(r *sessionRecord) { s.stPrevJournalNum = r.prevJournalNum } - if r.has(recSeq) { - s.stSeq = r.seq + if r.has(recSeqNum) { + s.stSeqNum = r.seqNum } - for _, p := range r.compactionPointers { - s.stCptrs[p.level] = iKey(p.ikey) + for _, p := range r.compPtrs { + s.stCompPtrs[p.level] = iKey(p.ikey) } } @@ -178,10 +178,11 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) { jw := journal.NewWriter(writer) if v == nil { - v = s.version_NB() + v = s.version() + defer v.release() } if rec == nil { - rec = new(sessionRecord) + rec = &sessionRecord{numLevel: s.o.GetNumLevel()} } s.fillRecord(rec, true) v.fillRecord(rec) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go index 5a1885e6..85dd70b0 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go @@ -125,3 +125,33 @@ type Storage interface { // Other methods should not be called after the storage has been closed. Close() error } + +// FileInfo wraps basic file info. +type FileInfo struct { + Type FileType + Num uint64 +} + +func (fi FileInfo) String() string { + switch fi.Type { + case TypeManifest: + return fmt.Sprintf("MANIFEST-%06d", fi.Num) + case TypeJournal: + return fmt.Sprintf("%06d.log", fi.Num) + case TypeTable: + return fmt.Sprintf("%06d.ldb", fi.Num) + case TypeTemp: + return fmt.Sprintf("%06d.tmp", fi.Num) + default: + return fmt.Sprintf("%#x-%d", fi.Type, fi.Num) + } +} + +// NewFileInfo creates new FileInfo from the given File. It will returns nil +// if File is nil. +func NewFileInfo(f File) *FileInfo { + if f == nil { + return nil + } + return &FileInfo{f.Type(), f.Num()} +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go index 27e76d70..dc1f1fb5 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/storage_test.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "os" "path/filepath" "sync" @@ -28,11 +29,25 @@ var ( ) var ( - tsFSEnv = os.Getenv("GOLEVELDB_USEFS") - tsKeepFS = tsFSEnv == "2" - tsFS = tsKeepFS || tsFSEnv == "" || tsFSEnv == "1" - tsMU = &sync.Mutex{} - tsNum = 0 + tsFSEnv = os.Getenv("GOLEVELDB_USEFS") + tsTempdir = os.Getenv("GOLEVELDB_TEMPDIR") + tsKeepFS = tsFSEnv == "2" + tsFS = tsKeepFS || tsFSEnv == "" || tsFSEnv == "1" + tsMU = &sync.Mutex{} + tsNum = 0 +) + +type tsOp uint + +const ( + tsOpOpen tsOp = iota + tsOpCreate + tsOpRead + tsOpReadAt + tsOpWrite + tsOpSync + + tsOpNum ) type tsLock struct { @@ -53,6 +68,9 @@ type tsReader struct { func (tr tsReader) Read(b []byte) (n int, err error) { ts := tr.tf.ts ts.countRead(tr.tf.Type()) + if tr.tf.shouldErrLocked(tsOpRead) { + return 0, errors.New("leveldb.testStorage: emulated read error") + } n, err = tr.Reader.Read(b) if err != nil && err != io.EOF { ts.t.Errorf("E: read error, num=%d type=%v n=%d: %v", tr.tf.Num(), tr.tf.Type(), n, err) @@ -63,6 +81,9 @@ func (tr tsReader) Read(b []byte) (n int, err error) { func (tr tsReader) ReadAt(b []byte, off int64) (n int, err error) { ts := tr.tf.ts ts.countRead(tr.tf.Type()) + if tr.tf.shouldErrLocked(tsOpReadAt) { + return 0, errors.New("leveldb.testStorage: emulated readAt error") + } n, err = tr.Reader.ReadAt(b, off) if err != nil && err != io.EOF { ts.t.Errorf("E: readAt error, num=%d type=%v off=%d n=%d: %v", tr.tf.Num(), tr.tf.Type(), off, n, err) @@ -82,15 +103,12 @@ type tsWriter struct { } func (tw tsWriter) Write(b []byte) (n int, err error) { - ts := tw.tf.ts - ts.mu.Lock() - defer ts.mu.Unlock() - if ts.emuWriteErr&tw.tf.Type() != 0 { + if tw.tf.shouldErrLocked(tsOpWrite) { return 0, errors.New("leveldb.testStorage: emulated write error") } n, err = tw.Writer.Write(b) if err != nil { - ts.t.Errorf("E: write error, num=%d type=%v n=%d: %v", tw.tf.Num(), tw.tf.Type(), n, err) + tw.tf.ts.t.Errorf("E: write error, num=%d type=%v n=%d: %v", tw.tf.Num(), tw.tf.Type(), n, err) } return } @@ -98,23 +116,23 @@ func (tw tsWriter) Write(b []byte) (n int, err error) { func (tw tsWriter) Sync() (err error) { ts := tw.tf.ts ts.mu.Lock() - defer ts.mu.Unlock() for ts.emuDelaySync&tw.tf.Type() != 0 { ts.cond.Wait() } - if ts.emuSyncErr&tw.tf.Type() != 0 { + ts.mu.Unlock() + if tw.tf.shouldErrLocked(tsOpSync) { return errors.New("leveldb.testStorage: emulated sync error") } err = tw.Writer.Sync() if err != nil { - ts.t.Errorf("E: sync error, num=%d type=%v: %v", tw.tf.Num(), tw.tf.Type(), err) + tw.tf.ts.t.Errorf("E: sync error, num=%d type=%v: %v", tw.tf.Num(), tw.tf.Type(), err) } return } func (tw tsWriter) Close() (err error) { err = tw.Writer.Close() - tw.tf.close("reader", err) + tw.tf.close("writer", err) return } @@ -127,6 +145,16 @@ func (tf tsFile) x() uint64 { return tf.Num()< 0 { // Find the earliest possible internal key for min. - i = tf.searchMax(icmp, newIKey(umin, kMaxSeq, tSeek)) + i = tf.searchMax(icmp, newIkey(umin, kMaxSeq, ktSeek)) } if i >= len(tf) { // Beginning of range is after all files, so no overlap. @@ -161,24 +172,25 @@ func (tf tFiles) overlaps(icmp *iComparer, umin, umax []byte, unsorted bool) boo } // Returns tables whose its key range overlaps with given key range. -// If overlapped is true then the search will be expanded to tables that -// overlaps with each other. +// Range will be expanded if ukey found hop across tables. +// If overlapped is true then the search will be restarted if umax +// expanded. +// The dst content will be overwritten. func (tf tFiles) getOverlaps(dst tFiles, icmp *iComparer, umin, umax []byte, overlapped bool) tFiles { - x := len(dst) + dst = dst[:0] for i := 0; i < len(tf); { t := tf[i] if t.overlaps(icmp, umin, umax) { - if overlapped { - // For overlapped files, check if the newly added file has - // expanded the range. If so, restart search. - if umin != nil && icmp.uCompare(t.imin.ukey(), umin) < 0 { - umin = t.imin.ukey() - dst = dst[:x] - i = 0 - continue - } else if umax != nil && icmp.uCompare(t.imax.ukey(), umax) > 0 { - umax = t.imax.ukey() - dst = dst[:x] + if umin != nil && icmp.uCompare(t.imin.ukey(), umin) < 0 { + umin = t.imin.ukey() + dst = dst[:0] + i = 0 + continue + } else if umax != nil && icmp.uCompare(t.imax.ukey(), umax) > 0 { + umax = t.imax.ukey() + // Restart search if it is overlapped. + if overlapped { + dst = dst[:0] i = 0 continue } @@ -278,8 +290,6 @@ type tOps struct { cache cache.Cache cacheNS cache.Namespace bpool *util.BufferPool - mu sync.Mutex - closed bool } // Creates an empty table and returns table writer. @@ -293,7 +303,7 @@ func (t *tOps) create() (*tWriter, error) { t: t, file: file, w: fw, - tw: table.NewWriter(fw, t.s.o), + tw: table.NewWriter(fw, t.s.o.Options), }, nil } @@ -326,42 +336,9 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) { return } -type trWrapper struct { - *table.Reader - t *tOps - ref int -} - -func (w *trWrapper) Release() { - if w.ref != 0 && !w.t.closed { - panic(fmt.Sprintf("BUG: invalid ref %d, refer to issue #72", w.ref)) - } - w.Reader.Release() -} - -type trCacheHandleWrapper struct { - cache.Handle - t *tOps - released bool -} - -func (w *trCacheHandleWrapper) Release() { - w.t.mu.Lock() - defer w.t.mu.Unlock() - - if !w.released { - w.released = true - w.Value().(*trWrapper).ref-- - } - w.Handle.Release() -} - // Opens table. It returns a cache handle, which should // be released after use. func (t *tOps) open(f *tFile) (ch cache.Handle, err error) { - t.mu.Lock() - defer t.mu.Unlock() - num := f.file.Num() ch = t.cacheNS.Get(num, func() (charge int, value interface{}) { var r storage.Reader @@ -374,13 +351,17 @@ func (t *tOps) open(f *tFile) (ch cache.Handle, err error) { if bc := t.s.o.GetBlockCache(); bc != nil { bcacheNS = bc.GetNamespace(num) } - return 1, &trWrapper{table.NewReader(r, int64(f.size), bcacheNS, t.bpool, t.s.o), t, 0} + var tr *table.Reader + tr, err = table.NewReader(r, int64(f.size), storage.NewFileInfo(f.file), bcacheNS, t.bpool, t.s.o.Options) + if err != nil { + r.Close() + return 0, nil + } + return 1, tr }) if ch == nil && err == nil { err = ErrClosed } - ch.Value().(*trWrapper).ref++ - ch = &trCacheHandleWrapper{ch, t, false} return } @@ -392,7 +373,17 @@ func (t *tOps) find(f *tFile, key []byte, ro *opt.ReadOptions) (rkey, rvalue []b return nil, nil, err } defer ch.Release() - return ch.Value().(*trWrapper).Find(key, ro) + return ch.Value().(*table.Reader).Find(key, true, ro) +} + +// Finds key that is greater than or equal to the given key. +func (t *tOps) findKey(f *tFile, key []byte, ro *opt.ReadOptions) (rkey []byte, err error) { + ch, err := t.open(f) + if err != nil { + return nil, err + } + defer ch.Release() + return ch.Value().(*table.Reader).FindKey(key, true, ro) } // Returns approximate offset of the given key. @@ -402,7 +393,7 @@ func (t *tOps) offsetOf(f *tFile, key []byte) (offset uint64, err error) { return } defer ch.Release() - offset_, err := ch.Value().(*trWrapper).OffsetOf(key) + offset_, err := ch.Value().(*table.Reader).OffsetOf(key) return uint64(offset_), err } @@ -412,7 +403,7 @@ func (t *tOps) newIterator(f *tFile, slice *util.Range, ro *opt.ReadOptions) ite if err != nil { return iterator.NewEmptyIterator(err) } - iter := ch.Value().(*trWrapper).NewIterator(slice, ro) + iter := ch.Value().(*table.Reader).NewIterator(slice, ro) iter.SetReleaser(ch) return iter } @@ -420,9 +411,6 @@ func (t *tOps) newIterator(f *tFile, slice *util.Range, ro *opt.ReadOptions) ite // Removes table from persistent storage. It waits until // no one use the the table. func (t *tOps) remove(f *tFile) { - t.mu.Lock() - defer t.mu.Unlock() - num := f.file.Num() t.cacheNS.Delete(num, func(exist, pending bool) { if !pending { @@ -441,10 +429,6 @@ func (t *tOps) remove(f *tFile) { // Closes the table ops instance. It will close all tables, // regadless still used or not. func (t *tOps) close() { - t.mu.Lock() - defer t.mu.Unlock() - - t.closed = true t.cache.Zap() t.bpool.Close() } @@ -486,28 +470,34 @@ func (w *tWriter) empty() bool { return w.first == nil } +// Closes the storage.Writer. +func (w *tWriter) close() { + if w.w != nil { + w.w.Close() + w.w = nil + } +} + // Finalizes the table and returns table file. func (w *tWriter) finish() (f *tFile, err error) { + defer w.close() err = w.tw.Close() if err != nil { return } err = w.w.Sync() if err != nil { - w.w.Close() return } - w.w.Close() f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last)) return } // Drops the table. func (w *tWriter) drop() { - w.w.Close() + w.close() w.file.Remove() w.t.s.reuseFileNum(w.file.Num()) - w.w = nil w.file = nil w.tw = nil w.first = nil diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go index f3b53c09..00e6f9ee 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go @@ -19,13 +19,18 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -func (b *block) TestNewIterator(slice *util.Range) iterator.Iterator { - return b.newIterator(slice, false, nil) +type blockTesting struct { + tr *Reader + b *block +} + +func (t *blockTesting) TestNewIterator(slice *util.Range) iterator.Iterator { + return t.tr.newBlockIter(t.b, nil, slice, false) } var _ = testutil.Defer(func() { Describe("Block", func() { - Build := func(kv *testutil.KeyValue, restartInterval int) *block { + Build := func(kv *testutil.KeyValue, restartInterval int) *blockTesting { // Building the block. bw := &blockWriter{ restartInterval: restartInterval, @@ -39,11 +44,13 @@ var _ = testutil.Defer(func() { // Opening the block. data := bw.buf.Bytes() restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:])) - return &block{ - tr: &Reader{cmp: comparer.DefaultComparer}, - data: data, - restartsLen: restartsLen, - restartsOffset: len(data) - (restartsLen+1)*4, + return &blockTesting{ + tr: &Reader{cmp: comparer.DefaultComparer}, + b: &block{ + data: data, + restartsLen: restartsLen, + restartsOffset: len(data) - (restartsLen+1)*4, + }, } } @@ -102,11 +109,11 @@ var _ = testutil.Defer(func() { for restartInterval := 1; restartInterval <= 5; restartInterval++ { Describe(fmt.Sprintf("with restart interval of %d", restartInterval), func() { // Make block. - br := Build(kv, restartInterval) + bt := Build(kv, restartInterval) Test := func(r *util.Range) func(done Done) { return func(done Done) { - iter := br.newIterator(r, false, nil) + iter := bt.TestNewIterator(r) Expect(iter.Error()).ShouldNot(HaveOccurred()) t := testutil.IteratorTesting{ @@ -115,6 +122,7 @@ var _ = testutil.Defer(func() { } testutil.DoIteratorTesting(&t) + iter.Release() done <- true } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go index ab62c44e..3b574462 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/reader.go @@ -8,29 +8,41 @@ package table import ( "encoding/binary" - "errors" "fmt" "io" "sort" "strings" "sync" - "code.google.com/p/snappy-go/snappy" + "github.com/syndtr/gosnappy/snappy" "github.com/syndtr/goleveldb/leveldb/cache" "github.com/syndtr/goleveldb/leveldb/comparer" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/util" ) var ( - ErrNotFound = util.ErrNotFound + ErrNotFound = errors.ErrNotFound ErrReaderReleased = errors.New("leveldb/table: reader released") ErrIterReleased = errors.New("leveldb/table: iterator released") ) +type ErrCorrupted struct { + Pos int64 + Size int64 + Kind string + Reason string +} + +func (e *ErrCorrupted) Error() string { + return fmt.Sprintf("leveldb/table: corruption on %s (pos=%d): %s", e.Kind, e.Pos, e.Reason) +} + func max(x, y int) int { if x > y { return x @@ -39,22 +51,21 @@ func max(x, y int) int { } type block struct { - tr *Reader + bpool *util.BufferPool + bh blockHandle data []byte restartsLen int restartsOffset int - // Whether checksum is verified and valid. - checksum bool } -func (b *block) seek(rstart, rlimit int, key []byte) (index, offset int, err error) { +func (b *block) seek(cmp comparer.Comparer, rstart, rlimit int, key []byte) (index, offset int, err error) { index = sort.Search(b.restartsLen-rstart-(b.restartsLen-rlimit), func(i int) bool { offset := int(binary.LittleEndian.Uint32(b.data[b.restartsOffset+4*(rstart+i):])) offset += 1 // shared always zero, since this is a restart point v1, n1 := binary.Uvarint(b.data[offset:]) // key length _, n2 := binary.Uvarint(b.data[offset+n1:]) // value length m := offset + n1 + n2 - return b.tr.cmp.Compare(b.data[m:m+int(v1)], key) > 0 + return cmp.Compare(b.data[m:m+int(v1)], key) > 0 }) + rstart - 1 if index < rstart { // The smallest key is greater-than key sought. @@ -77,7 +88,7 @@ func (b *block) restartOffset(index int) int { func (b *block) entry(offset int) (key, value []byte, nShared, n int, err error) { if offset >= b.restartsOffset { if offset != b.restartsOffset { - err = errors.New("leveldb/table: Reader: BlockEntry: invalid block (block entries offset not aligned)") + err = &ErrCorrupted{Reason: "entries offset not aligned"} } return } @@ -87,7 +98,7 @@ func (b *block) entry(offset int) (key, value []byte, nShared, n int, err error) m := n0 + n1 + n2 n = m + int(v1) + int(v2) if n0 <= 0 || n1 <= 0 || n2 <= 0 || offset+n > b.restartsOffset { - err = errors.New("leveldb/table: Reader: invalid block (block entries corrupted)") + err = &ErrCorrupted{Reason: "entries corrupted"} return } key = b.data[offset+m : offset+m+int(v1)] @@ -96,48 +107,9 @@ func (b *block) entry(offset int) (key, value []byte, nShared, n int, err error) return } -func (b *block) newIterator(slice *util.Range, inclLimit bool, cache util.Releaser) *blockIter { - bi := &blockIter{ - block: b, - cache: cache, - // Valid key should never be nil. - key: make([]byte, 0), - dir: dirSOI, - riStart: 0, - riLimit: b.restartsLen, - offsetStart: 0, - offsetRealStart: 0, - offsetLimit: b.restartsOffset, - } - if slice != nil { - if slice.Start != nil { - if bi.Seek(slice.Start) { - bi.riStart = b.restartIndex(bi.restartIndex, b.restartsLen, bi.prevOffset) - bi.offsetStart = b.restartOffset(bi.riStart) - bi.offsetRealStart = bi.prevOffset - } else { - bi.riStart = b.restartsLen - bi.offsetStart = b.restartsOffset - bi.offsetRealStart = b.restartsOffset - } - } - if slice.Limit != nil { - if bi.Seek(slice.Limit) && (!inclLimit || bi.Next()) { - bi.offsetLimit = bi.prevOffset - bi.riLimit = bi.restartIndex + 1 - } - } - bi.reset() - if bi.offsetStart > bi.offsetLimit { - bi.sErr(errors.New("leveldb/table: Reader: invalid slice range")) - } - } - return bi -} - func (b *block) Release() { - b.tr.bpool.Put(b.data) - b.tr = nil + b.bpool.Put(b.data) + b.bpool = nil b.data = nil } @@ -152,10 +124,12 @@ const ( ) type blockIter struct { - block *block - cache, releaser util.Releaser - key, value []byte - offset int + tr *Reader + block *block + blockReleaser util.Releaser + releaser util.Releaser + key, value []byte + offset int // Previous offset, only filled by Next. prevOffset int prevNode []int @@ -252,7 +226,7 @@ func (i *blockIter) Seek(key []byte) bool { return false } - ri, offset, err := i.block.seek(i.riStart, i.riLimit, key) + ri, offset, err := i.block.seek(i.tr.cmp, i.riStart, i.riLimit, key) if err != nil { i.sErr(err) return false @@ -263,7 +237,7 @@ func (i *blockIter) Seek(key []byte) bool { i.dir = dirForward } for i.Next() { - if i.block.tr.cmp.Compare(i.key, key) >= 0 { + if i.tr.cmp.Compare(i.key, key) >= 0 { return true } } @@ -288,7 +262,7 @@ func (i *blockIter) Next() bool { for i.offset < i.offsetRealStart { key, value, nShared, n, err := i.block.entry(i.offset) if err != nil { - i.sErr(err) + i.sErr(i.tr.fixErrCorruptedBH(i.block.bh, err)) return false } if n == 0 { @@ -302,13 +276,13 @@ func (i *blockIter) Next() bool { if i.offset >= i.offsetLimit { i.dir = dirEOI if i.offset != i.offsetLimit { - i.sErr(errors.New("leveldb/table: Reader: Next: invalid block (block entries offset not aligned)")) + i.sErr(i.tr.newErrCorruptedBH(i.block.bh, "entries offset not aligned")) } return false } key, value, nShared, n, err := i.block.entry(i.offset) if err != nil { - i.sErr(err) + i.sErr(i.tr.fixErrCorruptedBH(i.block.bh, err)) return false } if n == 0 { @@ -393,7 +367,7 @@ func (i *blockIter) Prev() bool { for { key, value, nShared, n, err := i.block.entry(offset) if err != nil { - i.sErr(err) + i.sErr(i.tr.fixErrCorruptedBH(i.block.bh, err)) return false } if offset >= i.offsetRealStart { @@ -412,7 +386,7 @@ func (i *blockIter) Prev() bool { // Stop if target offset reached. if offset >= i.offset { if offset != i.offset { - i.sErr(errors.New("leveldb/table: Reader: Prev: invalid block (block entries offset not aligned)")) + i.sErr(i.tr.newErrCorruptedBH(i.block.bh, "entries offset not aligned")) return false } @@ -440,15 +414,16 @@ func (i *blockIter) Value() []byte { func (i *blockIter) Release() { if i.dir != dirReleased { + i.tr = nil i.block = nil i.prevNode = nil i.prevKeys = nil i.key = nil i.value = nil i.dir = dirReleased - if i.cache != nil { - i.cache.Release() - i.cache = nil + if i.blockReleaser != nil { + i.blockReleaser.Release() + i.blockReleaser = nil } if i.releaser != nil { i.releaser.Release() @@ -476,21 +451,21 @@ func (i *blockIter) Error() error { } type filterBlock struct { - tr *Reader + bpool *util.BufferPool data []byte oOffset int baseLg uint filtersNum int } -func (b *filterBlock) contains(offset uint64, key []byte) bool { +func (b *filterBlock) contains(filter filter.Filter, offset uint64, key []byte) bool { i := int(offset >> b.baseLg) if i < b.filtersNum { o := b.data[b.oOffset+i*4:] n := int(binary.LittleEndian.Uint32(o)) m := int(binary.LittleEndian.Uint32(o[4:])) if n < m && m <= b.oOffset { - return b.tr.filter.Contains(b.data[n:m], key) + return filter.Contains(b.data[n:m], key) } else if n == m { return false } @@ -499,16 +474,16 @@ func (b *filterBlock) contains(offset uint64, key []byte) bool { } func (b *filterBlock) Release() { - b.tr.bpool.Put(b.data) - b.tr = nil + b.bpool.Put(b.data) + b.bpool = nil b.data = nil } type indexIter struct { *blockIter + tr *Reader slice *util.Range // Options - checksum bool fillCache bool } @@ -519,91 +494,124 @@ func (i *indexIter) Get() iterator.Iterator { } dataBH, n := decodeBlockHandle(value) if n == 0 { - return iterator.NewEmptyIterator(errors.New("leveldb/table: Reader: invalid table (bad data block handle)")) + return iterator.NewEmptyIterator(i.tr.newErrCorruptedBH(i.tr.indexBH, "bad data block handle")) } var slice *util.Range if i.slice != nil && (i.blockIter.isFirst() || i.blockIter.isLast()) { slice = i.slice } - return i.blockIter.block.tr.getDataIterErr(dataBH, slice, i.checksum, i.fillCache) + return i.tr.getDataIterErr(dataBH, slice, i.tr.verifyChecksum, i.fillCache) } // Reader is a table reader. type Reader struct { mu sync.RWMutex + fi *storage.FileInfo reader io.ReaderAt cache cache.Namespace err error bpool *util.BufferPool // Options - cmp comparer.Comparer - filter filter.Filter - checksum bool - strictIter bool + o *opt.Options + cmp comparer.Comparer + filter filter.Filter + verifyChecksum bool - dataEnd int64 - indexBH, filterBH blockHandle - indexBlock *block - filterBlock *filterBlock + dataEnd int64 + metaBH, indexBH, filterBH blockHandle + indexBlock *block + filterBlock *filterBlock } -func verifyChecksum(data []byte) bool { - n := len(data) - 4 - checksum0 := binary.LittleEndian.Uint32(data[n:]) - checksum1 := util.NewCRC(data[:n]).Value() - return checksum0 == checksum1 +func (r *Reader) blockKind(bh blockHandle) string { + switch bh.offset { + case r.metaBH.offset: + return "meta-block" + case r.indexBH.offset: + return "index-block" + case r.filterBH.offset: + if r.filterBH.length > 0 { + return "filter-block" + } + } + return "data-block" } -func (r *Reader) readRawBlock(bh blockHandle, checksum bool) ([]byte, error) { +func (r *Reader) newErrCorrupted(pos, size int64, kind, reason string) error { + return &errors.ErrCorrupted{File: r.fi, Err: &ErrCorrupted{Pos: pos, Size: size, Kind: kind, Reason: reason}} +} + +func (r *Reader) newErrCorruptedBH(bh blockHandle, reason string) error { + return r.newErrCorrupted(int64(bh.offset), int64(bh.length), r.blockKind(bh), reason) +} + +func (r *Reader) fixErrCorruptedBH(bh blockHandle, err error) error { + if cerr, ok := err.(*ErrCorrupted); ok { + cerr.Pos = int64(bh.offset) + cerr.Size = int64(bh.length) + cerr.Kind = r.blockKind(bh) + return &errors.ErrCorrupted{File: r.fi, Err: cerr} + } + return err +} + +func (r *Reader) readRawBlock(bh blockHandle, verifyChecksum bool) ([]byte, error) { data := r.bpool.Get(int(bh.length + blockTrailerLen)) if _, err := r.reader.ReadAt(data, int64(bh.offset)); err != nil && err != io.EOF { return nil, err } - if checksum || r.checksum { - if !verifyChecksum(data) { + + if verifyChecksum { + n := bh.length + 1 + checksum0 := binary.LittleEndian.Uint32(data[n:]) + checksum1 := util.NewCRC(data[:n]).Value() + if checksum0 != checksum1 { r.bpool.Put(data) - return nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)") + return nil, r.newErrCorruptedBH(bh, fmt.Sprintf("checksum mismatch, want=%#x got=%#x", checksum0, checksum1)) } } + switch data[bh.length] { case blockTypeNoCompression: data = data[:bh.length] case blockTypeSnappyCompression: decLen, err := snappy.DecodedLen(data[:bh.length]) if err != nil { - return nil, err + return nil, r.newErrCorruptedBH(bh, err.Error()) } - tmp := data - data, err = snappy.Decode(r.bpool.Get(decLen), tmp[:bh.length]) - r.bpool.Put(tmp) + decData := r.bpool.Get(decLen) + decData, err = snappy.Decode(decData, data[:bh.length]) + r.bpool.Put(data) if err != nil { - return nil, err + r.bpool.Put(decData) + return nil, r.newErrCorruptedBH(bh, err.Error()) } + data = decData default: r.bpool.Put(data) - return nil, fmt.Errorf("leveldb/table: Reader: unknown block compression type: %d", data[bh.length]) + return nil, r.newErrCorruptedBH(bh, fmt.Sprintf("unknown compression type %#x", data[bh.length])) } return data, nil } -func (r *Reader) readBlock(bh blockHandle, checksum bool) (*block, error) { - data, err := r.readRawBlock(bh, checksum) +func (r *Reader) readBlock(bh blockHandle, verifyChecksum bool) (*block, error) { + data, err := r.readRawBlock(bh, verifyChecksum) if err != nil { return nil, err } restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:])) b := &block{ - tr: r, + bpool: r.bpool, + bh: bh, data: data, restartsLen: restartsLen, restartsOffset: len(data) - (restartsLen+1)*4, - checksum: checksum || r.checksum, } return b, nil } -func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*block, util.Releaser, error) { +func (r *Reader) readBlockCached(bh blockHandle, verifyChecksum, fillCache bool) (*block, util.Releaser, error) { if r.cache != nil { var err error ch := r.cache.Get(bh.offset, func() (charge int, value interface{}) { @@ -611,7 +619,7 @@ func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*blo return 0, nil } var b *block - b, err = r.readBlock(bh, checksum) + b, err = r.readBlock(bh, verifyChecksum) if err != nil { return 0, nil } @@ -621,14 +629,7 @@ func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*blo b, ok := ch.Value().(*block) if !ok { ch.Release() - return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type") - } - if !b.checksum && (r.checksum || checksum) { - if !verifyChecksum(b.data) { - ch.Release() - return nil, nil, errors.New("leveldb/table: Reader: invalid block (checksum mismatch)") - } - b.checksum = true + return nil, nil, errors.New("leveldb/table: inconsistent block type") } return b, ch, err } else if err != nil { @@ -636,7 +637,7 @@ func (r *Reader) readBlockCached(bh blockHandle, checksum, fillCache bool) (*blo } } - b, err := r.readBlock(bh, checksum) + b, err := r.readBlock(bh, verifyChecksum) return b, b, err } @@ -647,15 +648,15 @@ func (r *Reader) readFilterBlock(bh blockHandle) (*filterBlock, error) { } n := len(data) if n < 5 { - return nil, errors.New("leveldb/table: Reader: invalid filter block (too short)") + return nil, r.newErrCorruptedBH(bh, "too short") } m := n - 5 oOffset := int(binary.LittleEndian.Uint32(data[m:])) if oOffset > m { - return nil, errors.New("leveldb/table: Reader: invalid filter block (invalid offset)") + return nil, r.newErrCorruptedBH(bh, "invalid data-offsets offset") } b := &filterBlock{ - tr: r, + bpool: r.bpool, data: data, oOffset: oOffset, baseLg: uint(data[n-1]), @@ -682,7 +683,7 @@ func (r *Reader) readFilterBlockCached(bh blockHandle, fillCache bool) (*filterB b, ok := ch.Value().(*filterBlock) if !ok { ch.Release() - return nil, nil, errors.New("leveldb/table: Reader: inconsistent block type") + return nil, nil, errors.New("leveldb/table: inconsistent block type") } return b, ch, err } else if err != nil { @@ -708,15 +709,55 @@ func (r *Reader) getFilterBlock(fillCache bool) (*filterBlock, util.Releaser, er return r.filterBlock, util.NoopReleaser{}, nil } -func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator { - b, rel, err := r.readBlockCached(dataBH, checksum, fillCache) +func (r *Reader) newBlockIter(b *block, bReleaser util.Releaser, slice *util.Range, inclLimit bool) *blockIter { + bi := &blockIter{ + tr: r, + block: b, + blockReleaser: bReleaser, + // Valid key should never be nil. + key: make([]byte, 0), + dir: dirSOI, + riStart: 0, + riLimit: b.restartsLen, + offsetStart: 0, + offsetRealStart: 0, + offsetLimit: b.restartsOffset, + } + if slice != nil { + if slice.Start != nil { + if bi.Seek(slice.Start) { + bi.riStart = b.restartIndex(bi.restartIndex, b.restartsLen, bi.prevOffset) + bi.offsetStart = b.restartOffset(bi.riStart) + bi.offsetRealStart = bi.prevOffset + } else { + bi.riStart = b.restartsLen + bi.offsetStart = b.restartsOffset + bi.offsetRealStart = b.restartsOffset + } + } + if slice.Limit != nil { + if bi.Seek(slice.Limit) && (!inclLimit || bi.Next()) { + bi.offsetLimit = bi.prevOffset + bi.riLimit = bi.restartIndex + 1 + } + } + bi.reset() + if bi.offsetStart > bi.offsetLimit { + bi.sErr(errors.New("leveldb/table: invalid slice range")) + } + } + return bi +} + +func (r *Reader) getDataIter(dataBH blockHandle, slice *util.Range, verifyChecksum, fillCache bool) iterator.Iterator { + b, rel, err := r.readBlockCached(dataBH, verifyChecksum, fillCache) if err != nil { return iterator.NewEmptyIterator(err) } - return b.newIterator(slice, false, rel) + return r.newBlockIter(b, rel, slice, false) } -func (r *Reader) getDataIterErr(dataBH blockHandle, slice *util.Range, checksum, fillCache bool) iterator.Iterator { +func (r *Reader) getDataIterErr(dataBH blockHandle, slice *util.Range, verifyChecksum, fillCache bool) iterator.Iterator { r.mu.RLock() defer r.mu.RUnlock() @@ -724,7 +765,7 @@ func (r *Reader) getDataIterErr(dataBH blockHandle, slice *util.Range, checksum, return iterator.NewEmptyIterator(r.err) } - return r.getDataIter(dataBH, slice, checksum, fillCache) + return r.getDataIter(dataBH, slice, verifyChecksum, fillCache) } // NewIterator creates an iterator from the table. @@ -752,21 +793,15 @@ func (r *Reader) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It return iterator.NewEmptyIterator(err) } index := &indexIter{ - blockIter: indexBlock.newIterator(slice, true, rel), + blockIter: r.newBlockIter(indexBlock, rel, slice, true), + tr: r, slice: slice, - checksum: ro.GetStrict(opt.StrictBlockChecksum), fillCache: !ro.GetDontFillCache(), } - return iterator.NewIndexedIterator(index, r.strictIter || ro.GetStrict(opt.StrictIterator), true) + return iterator.NewIndexedIterator(index, opt.GetStrict(r.o, ro, opt.StrictReader)) } -// Find finds key/value pair whose key is greater than or equal to the -// given key. It returns ErrNotFound if the table doesn't contain -// such pair. -// -// The caller should not modify the contents of the returned slice, but -// it is safe to modify the contents of the argument after Find returns. -func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err error) { +func (r *Reader) find(key []byte, filtered bool, ro *opt.ReadOptions, noValue bool) (rkey, value []byte, err error) { r.mu.RLock() defer r.mu.RUnlock() @@ -781,7 +816,7 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err } defer rel.Release() - index := indexBlock.newIterator(nil, true, nil) + index := r.newBlockIter(indexBlock, nil, nil, true) defer index.Release() if !index.Seek(key) { err = index.Error() @@ -792,20 +827,23 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err } dataBH, n := decodeBlockHandle(index.Value()) if n == 0 { - err = errors.New("leveldb/table: Reader: invalid table (bad data block handle)") + r.err = r.newErrCorruptedBH(r.indexBH, "bad data block handle") return } - if r.filter != nil { - filterBlock, rel, ferr := r.getFilterBlock(true) + if filtered && r.filter != nil { + filterBlock, frel, ferr := r.getFilterBlock(true) if ferr == nil { - if !filterBlock.contains(dataBH.offset, key) { - rel.Release() + if !filterBlock.contains(r.filter, dataBH.offset, key) { + frel.Release() return nil, nil, ErrNotFound } - rel.Release() + frel.Release() + } else if !errors.IsCorrupted(ferr) { + err = ferr + return } } - data := r.getDataIter(dataBH, nil, ro.GetStrict(opt.StrictBlockChecksum), !ro.GetDontFillCache()) + data := r.getDataIter(dataBH, nil, r.verifyChecksum, !ro.GetDontFillCache()) defer data.Release() if !data.Seek(key) { err = data.Error() @@ -816,21 +854,52 @@ func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err } // Don't use block buffer, no need to copy the buffer. rkey = data.Key() - if r.bpool == nil { - value = data.Value() - } else { - // Use block buffer, and since the buffer will be recycled, the buffer - // need to be copied. - value = append([]byte{}, data.Value()...) + if !noValue { + if r.bpool == nil { + value = data.Value() + } else { + // Use block buffer, and since the buffer will be recycled, the buffer + // need to be copied. + value = append([]byte{}, data.Value()...) + } } return } +// Find finds key/value pair whose key is greater than or equal to the +// given key. It returns ErrNotFound if the table doesn't contain +// such pair. +// If filtered is true then the nearest 'block' will be checked against +// 'filter data' (if present) and will immediately return ErrNotFound if +// 'filter data' indicates that such pair doesn't exist. +// +// The caller may modify the contents of the returned slice as it is its +// own copy. +// It is safe to modify the contents of the argument after Find returns. +func (r *Reader) Find(key []byte, filtered bool, ro *opt.ReadOptions) (rkey, value []byte, err error) { + return r.find(key, filtered, ro, false) +} + +// Find finds key that is greater than or equal to the given key. +// It returns ErrNotFound if the table doesn't contain such key. +// If filtered is true then the nearest 'block' will be checked against +// 'filter data' (if present) and will immediately return ErrNotFound if +// 'filter data' indicates that such key doesn't exist. +// +// The caller may modify the contents of the returned slice as it is its +// own copy. +// It is safe to modify the contents of the argument after Find returns. +func (r *Reader) FindKey(key []byte, filtered bool, ro *opt.ReadOptions) (rkey []byte, err error) { + rkey, _, err = r.find(key, filtered, ro, true) + return +} + // Get gets the value for the given key. It returns errors.ErrNotFound // if the table does not contain the key. // -// The caller should not modify the contents of the returned slice, but -// it is safe to modify the contents of the argument after Get returns. +// The caller may modify the contents of the returned slice as it is its +// own copy. +// It is safe to modify the contents of the argument after Find returns. func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) { r.mu.RLock() defer r.mu.RUnlock() @@ -840,7 +909,7 @@ func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) return } - rkey, value, err := r.Find(key, ro) + rkey, value, err := r.find(key, false, ro, false) if err == nil && r.cmp.Compare(rkey, key) != 0 { value = nil err = ErrNotFound @@ -866,12 +935,12 @@ func (r *Reader) OffsetOf(key []byte) (offset int64, err error) { } defer rel.Release() - index := indexBlock.newIterator(nil, true, nil) + index := r.newBlockIter(indexBlock, nil, nil, true) defer index.Release() if index.Seek(key) { dataBH, n := decodeBlockHandle(index.Value()) if n == 0 { - err = errors.New("leveldb/table: Reader: invalid table (bad data block handle)") + r.err = r.newErrCorruptedBH(r.indexBH, "bad data block handle") return } offset = int64(dataBH.offset) @@ -908,55 +977,70 @@ func (r *Reader) Release() { } // NewReader creates a new initialized table reader for the file. -// The cache and bpool is optional and can be nil. +// The fi, cache and bpool is optional and can be nil. // // The returned table reader instance is goroutine-safe. -func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, bpool *util.BufferPool, o *opt.Options) *Reader { - r := &Reader{ - reader: f, - cache: cache, - bpool: bpool, - cmp: o.GetComparer(), - checksum: o.GetStrict(opt.StrictBlockChecksum), - strictIter: o.GetStrict(opt.StrictIterator), - } +func NewReader(f io.ReaderAt, size int64, fi *storage.FileInfo, cache cache.Namespace, bpool *util.BufferPool, o *opt.Options) (*Reader, error) { if f == nil { - r.err = errors.New("leveldb/table: Reader: nil file") - return r + return nil, errors.New("leveldb/table: nil file") } + + r := &Reader{ + fi: fi, + reader: f, + cache: cache, + bpool: bpool, + o: o, + cmp: o.GetComparer(), + verifyChecksum: o.GetStrict(opt.StrictBlockChecksum), + } + if size < footerLen { - r.err = errors.New("leveldb/table: Reader: invalid table (file size is too small)") - return r + r.err = r.newErrCorrupted(0, size, "table", "too small") + return r, nil } + + footerPos := size - footerLen var footer [footerLen]byte - if _, err := r.reader.ReadAt(footer[:], size-footerLen); err != nil && err != io.EOF { - r.err = fmt.Errorf("leveldb/table: Reader: invalid table (could not read footer): %v", err) + if _, err := r.reader.ReadAt(footer[:], footerPos); err != nil && err != io.EOF { + return nil, err } if string(footer[footerLen-len(magic):footerLen]) != magic { - r.err = errors.New("leveldb/table: Reader: invalid table (bad magic number)") - return r + r.err = r.newErrCorrupted(footerPos, footerLen, "table-footer", "bad magic number") + return r, nil } + + var n int // Decode the metaindex block handle. - metaBH, n := decodeBlockHandle(footer[:]) + r.metaBH, n = decodeBlockHandle(footer[:]) if n == 0 { - r.err = errors.New("leveldb/table: Reader: invalid table (bad metaindex block handle)") - return r + r.err = r.newErrCorrupted(footerPos, footerLen, "table-footer", "bad metaindex block handle") + return r, nil } + // Decode the index block handle. r.indexBH, n = decodeBlockHandle(footer[n:]) if n == 0 { - r.err = errors.New("leveldb/table: Reader: invalid table (bad index block handle)") - return r + r.err = r.newErrCorrupted(footerPos, footerLen, "table-footer", "bad index block handle") + return r, nil } + // Read metaindex block. - metaBlock, err := r.readBlock(metaBH, true) + metaBlock, err := r.readBlock(r.metaBH, true) if err != nil { - r.err = err - return r + if errors.IsCorrupted(err) { + r.err = err + return r, nil + } else { + return nil, err + } } + // Set data end. - r.dataEnd = int64(metaBH.offset) - metaIter := metaBlock.newIterator(nil, false, nil) + r.dataEnd = int64(r.metaBH.offset) + + // Read metaindex. + metaIter := r.newBlockIter(metaBlock, nil, nil, true) for metaIter.Next() { key := string(metaIter.Key()) if !strings.HasPrefix(key, "filter.") { @@ -989,19 +1073,27 @@ func NewReader(f io.ReaderAt, size int64, cache cache.Namespace, bpool *util.Buf // Cache index and filter block locally, since we don't have global cache. if cache == nil { - r.indexBlock, r.err = r.readBlock(r.indexBH, true) - if r.err != nil { - return r + r.indexBlock, err = r.readBlock(r.indexBH, true) + if err != nil { + if errors.IsCorrupted(err) { + r.err = err + return r, nil + } else { + return nil, err + } } if r.filter != nil { r.filterBlock, err = r.readFilterBlock(r.filterBH) if err != nil { + if !errors.IsCorrupted(err) { + return nil, err + } + // Don't use filter then. r.filter = nil - r.filterBH = blockHandle{} } } } - return r + return r, nil } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table.go index c0ac70d9..beacdc1f 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table.go @@ -133,9 +133,9 @@ Filter block trailer: +- 4-bytes -+ / \ - +---------------+---------------+---------------+-------------------------+------------------+ - | offset 1 | .... | offset n | filter offset (4-bytes) | base Lg (1-byte) | - +-------------- +---------------+---------------+-------------------------+------------------+ + +---------------+---------------+---------------+-------------------------------+------------------+ + | data 1 offset | .... | data n offset | data-offsets offset (4-bytes) | base Lg (1-byte) | + +-------------- +---------------+---------------+-------------------------------+------------------+ NOTE: All fixed-length integer are little-endian. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go index bc9eb83c..6465da6e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go @@ -3,15 +3,9 @@ package table import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/syndtr/goleveldb/leveldb/testutil" ) func TestTable(t *testing.T) { - testutil.RunDefer() - - RegisterFailHandler(Fail) - RunSpecs(t, "Table Suite") + testutil.RunSuite(t, "Table Suite") } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go index 3e6e8583..4b59b31f 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go @@ -23,7 +23,7 @@ type tableWrapper struct { } func (t tableWrapper) TestFind(key []byte) (rkey, rvalue []byte, err error) { - return t.Reader.Find(key, nil) + return t.Reader.Find(key, false, nil) } func (t tableWrapper) TestGet(key []byte) (value []byte, err error) { @@ -59,7 +59,8 @@ var _ = testutil.Defer(func() { It("Should be able to approximate offset of a key correctly", func() { Expect(err).ShouldNot(HaveOccurred()) - tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o) + tr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, nil, o) + Expect(err).ShouldNot(HaveOccurred()) CheckOffset := func(key string, expect, threshold int) { offset, err := tr.OffsetOf([]byte(key)) Expect(err).ShouldNot(HaveOccurred()) @@ -95,7 +96,7 @@ var _ = testutil.Defer(func() { tw.Close() // Opening the table. - tr := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, o) + tr, _ := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, nil, o) return tableWrapper{tr} } Test := func(kv *testutil.KeyValue, body func(r *Reader)) func() { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/writer.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/writer.go index 4e19e93a..274c95fa 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/writer.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/table/writer.go @@ -12,7 +12,7 @@ import ( "fmt" "io" - "code.google.com/p/snappy-go/snappy" + "github.com/syndtr/gosnappy/snappy" "github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/filter" diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/db.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/db.go index 4b87b5ef..ec3f177a 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/db.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/db.go @@ -12,6 +12,7 @@ import ( . "github.com/onsi/gomega" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -34,6 +35,10 @@ type Get interface { TestGet(key []byte) (value []byte, err error) } +type Has interface { + TestHas(key []byte) (ret bool, err error) +} + type NewIterator interface { TestNewIterator(slice *util.Range) iterator.Iterator } @@ -110,7 +115,7 @@ func (t *DBTesting) TestAllPresent() { func (t *DBTesting) TestDeletedKey(key []byte) { _, err := t.DB.TestGet(key) - Expect(err).Should(Equal(util.ErrNotFound), "Get on deleted key %q, %s", key, t.text()) + Expect(err).Should(Equal(errors.ErrNotFound), "Get on deleted key %q, %s", key, t.text()) } func (t *DBTesting) TestAllDeleted() { @@ -212,5 +217,6 @@ func DoDBTesting(t *DBTesting) { } DoIteratorTesting(&it) + iter.Release() } } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/ginkgo.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/ginkgo.go new file mode 100644 index 00000000..82f3d0e8 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/ginkgo.go @@ -0,0 +1,21 @@ +package testutil + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func RunSuite(t GinkgoTestingT, name string) { + RunDefer() + + SynchronizedBeforeSuite(func() []byte { + RunDefer("setup") + return nil + }, func(data []byte) {}) + SynchronizedAfterSuite(func() { + RunDefer("teardown") + }, func() {}) + + RegisterFailHandler(Fail) + RunSpecs(t, name) +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go index 0cead03b..a0b58f0e 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go @@ -13,6 +13,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -25,9 +26,11 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, BeforeEach(func() { p = setup(kv) }) - AfterEach(func() { - teardown(p) - }) + if teardown != nil { + AfterEach(func() { + teardown(p) + }) + } } It("Should find all keys with Find", func() { @@ -59,7 +62,7 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, } rkey, _, err := db.TestFind(key) Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey) - Expect(err).Should(Equal(util.ErrNotFound)) + Expect(err).Should(Equal(errors.ErrNotFound)) } }) @@ -77,7 +80,27 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, if len(key_) > 0 { _, err = db.TestGet(key_) Expect(err).Should(HaveOccurred(), "Error for key %q", key_) - Expect(err).Should(Equal(util.ErrNotFound)) + Expect(err).Should(Equal(errors.ErrNotFound)) + } + }) + } + }) + + It("Should only find present key with Has", func() { + if db, ok := p.(Has); ok { + ShuffledIndex(nil, kv.Len(), 1, func(i int) { + key_, key, _ := kv.IndexInexact(i) + + // Using exact key. + ret, err := db.TestHas(key) + Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key) + Expect(ret).Should(BeTrue(), "False for key %q", key) + + // Using inexact key. + if len(key_) > 0 { + ret, err = db.TestHas(key_) + Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key_) + Expect(ret).ShouldNot(BeTrue(), "True for key %q", key) } }) } @@ -94,6 +117,7 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, } DoIteratorTesting(&t) + iter.Release() } } @@ -102,7 +126,7 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, done <- true }, 3.0) - RandomIndex(rnd, kv.Len(), kv.Len(), func(i int) { + RandomIndex(rnd, kv.Len(), Min(kv.Len(), 50), func(i int) { type slice struct { r *util.Range start, limit int @@ -120,7 +144,7 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, } }) - RandomRange(rnd, kv.Len(), kv.Len(), func(start, limit int) { + RandomRange(rnd, kv.Len(), Min(kv.Len(), 50), func(start, limit int) { It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) { r := kv.Range(start, limit) TestIter(&r, kv.Slice(start, limit)) @@ -133,10 +157,22 @@ func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown Test := func(kv *KeyValue) func() { return func() { var p DB + if setup != nil { + Defer("setup", func() { + p = setup(*kv) + }) + } + if teardown != nil { + Defer("teardown", func() { + teardown(p) + }) + } if body != nil { p = body(*kv) } - KeyValueTesting(rnd, *kv, p, setup, teardown) + KeyValueTesting(rnd, *kv, p, func(KeyValue) DB { + return p + }, nil) } } @@ -147,4 +183,5 @@ func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown Describe("with big value", Test(KeyValue_BigValue())) Describe("with special key", Test(KeyValue_SpecialKey())) Describe("with multiple key/value", Test(KeyValue_MultipleKeyValue())) + Describe("with generated key/value", Test(KeyValue_Generate(nil, 120, 1, 50, 10, 120))) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go index 0f8d77a7..59c496d5 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go @@ -397,6 +397,7 @@ func (s *Storage) logI(format string, args ...interface{}) { func (s *Storage) Log(str string) { s.log(1, "Log: "+str) + s.Storage.Log(str) } func (s *Storage) Lock() (r util.Releaser, err error) { diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/util.go index 38fe25d5..97c5294b 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil/util.go @@ -155,3 +155,17 @@ func RandomRange(rnd *rand.Rand, n, round int, fn func(start, limit int)) { } return } + +func Max(x, y int) int { + if x > y { + return x + } + return y +} + +func Min(x, y int) int { + if x < y { + return x + } + return y +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go index 111f8730..25bf2b29 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go @@ -34,6 +34,10 @@ func (t *testingDB) TestGet(key []byte) (value []byte, err error) { return t.Get(key, t.ro) } +func (t *testingDB) TestHas(key []byte) (ret bool, err error) { + return t.Has(key, t.ro) +} + func (t *testingDB) TestNewIterator(slice *util.Range) iterator.Iterator { return t.NewIterator(slice, t.ro) } diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util.go index a43d2e46..1a5bf71a 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util.go @@ -14,10 +14,10 @@ import ( ) func shorten(str string) string { - if len(str) <= 4 { + if len(str) <= 8 { return str } - return str[:1] + ".." + str[len(str)-1:] + return str[:3] + ".." + str[len(str)-3:] } var bunits = [...]string{"", "Ki", "Mi", "Gi"} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go index aea39dca..2b8453d7 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go @@ -8,6 +8,7 @@ package util import ( "fmt" + "sync" "sync/atomic" "time" ) @@ -19,17 +20,16 @@ type buffer struct { // BufferPool is a 'buffer pool'. type BufferPool struct { - pool [6]chan []byte - size [5]uint32 - sizeMiss [5]uint32 - sizeHalf [5]uint32 - baseline [4]int - baselinex0 int - baselinex1 int - baseline0 int - baseline1 int - baseline2 int - close chan struct{} + pool [6]chan []byte + size [5]uint32 + sizeMiss [5]uint32 + sizeHalf [5]uint32 + baseline [4]int + baseline0 int + + mu sync.RWMutex + closed bool + closeC chan struct{} get uint32 put uint32 @@ -58,6 +58,13 @@ func (p *BufferPool) Get(n int) []byte { return make([]byte, n) } + p.mu.RLock() + defer p.mu.RUnlock() + + if p.closed { + return make([]byte, n) + } + atomic.AddUint32(&p.get, 1) poolNum := p.poolNum(n) @@ -153,6 +160,13 @@ func (p *BufferPool) Put(b []byte) { return } + p.mu.RLock() + defer p.mu.RUnlock() + + if p.closed { + return + } + atomic.AddUint32(&p.put, 1) pool := p.pool[p.poolNum(cap(b))] @@ -168,10 +182,12 @@ func (p *BufferPool) Close() { return } - select { - case p.close <- struct{}{}: - default: + p.mu.Lock() + if !p.closed { + p.closed = true + p.closeC <- struct{}{} } + p.mu.Unlock() } func (p *BufferPool) String() string { @@ -194,7 +210,8 @@ func (p *BufferPool) drain() { default: } } - case <-p.close: + case <-p.closeC: + close(p.closeC) for _, ch := range p.pool { close(ch) } @@ -211,7 +228,7 @@ func NewBufferPool(baseline int) *BufferPool { p := &BufferPool{ baseline0: baseline, baseline: [...]int{baseline / 4, baseline / 2, baseline * 2, baseline * 4}, - close: make(chan struct{}, 1), + closeC: make(chan struct{}, 1), } for i, cap := range []int{2, 2, 4, 4, 2, 1} { p.pool[i] = make(chan []byte, cap) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go index f690e484..f3597686 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/util/util.go @@ -12,7 +12,6 @@ import ( ) var ( - ErrNotFound = errors.New("leveldb: not found") ErrReleased = errors.New("leveldb: resource already relesed") ErrHasReleaser = errors.New("leveldb: releaser already defined") ) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go index 81fd9ee0..5ab7b53d 100644 --- a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/goleveldb/leveldb/version.go @@ -7,7 +7,6 @@ package leveldb import ( - "errors" "sync/atomic" "unsafe" @@ -16,19 +15,6 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -var levelMaxSize [kNumLevels]float64 - -func init() { - // Precompute max size of each level - for level := range levelMaxSize { - res := float64(10 * 1048576) - for n := level; n > 1; n-- { - res *= 10 - } - levelMaxSize[level] = res - } -} - type tSet struct { level int table *tFile @@ -37,7 +23,7 @@ type tSet struct { type version struct { s *session - tables [kNumLevels]tFiles + tables []tFiles // Level that should be compacted next and its compaction score. // Score < 1 means compaction is not strictly needed. These fields @@ -47,11 +33,16 @@ type version struct { cSeek unsafe.Pointer - ref int + ref int + // Succeeding version. next *version } -func (v *version) release_NB() { +func newVersion(s *session) *version { + return &version{s: s, tables: make([]tFiles, s.o.GetNumLevel())} +} + +func (v *version) releaseNB() { v.ref-- if v.ref > 0 { return @@ -77,13 +68,13 @@ func (v *version) release_NB() { } } - v.next.release_NB() + v.next.releaseNB() v.next = nil } func (v *version) release() { v.s.vmu.Lock() - v.release_NB() + v.releaseNB() v.s.vmu.Unlock() } @@ -123,17 +114,18 @@ func (v *version) walkOverlapping(ikey iKey, f func(level int, t *tFile) bool, l } } -func (v *version) get(ikey iKey, ro *opt.ReadOptions) (value []byte, tcomp bool, err error) { +func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byte, tcomp bool, err error) { ukey := ikey.ukey() var ( tset *tSet tseek bool - l0found bool - l0seq uint64 - l0vt vType - l0val []byte + // Level-0. + zfound bool + zseq uint64 + zkt kType + zval []byte ) err = ErrNotFound @@ -150,55 +142,60 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions) (value []byte, tcomp bool, } } - ikey__, val_, err_ := v.s.tops.find(t, ikey, ro) - switch err_ { + var ( + fikey, fval []byte + ferr error + ) + if noValue { + fikey, ferr = v.s.tops.findKey(t, ikey, ro) + } else { + fikey, fval, ferr = v.s.tops.find(t, ikey, ro) + } + switch ferr { case nil: case ErrNotFound: return true default: - err = err_ + err = ferr return false } - ikey_ := iKey(ikey__) - if seq, vt, ok := ikey_.parseNum(); ok { - if v.s.icmp.uCompare(ukey, ikey_.ukey()) != 0 { - return true - } - - if level == 0 { - if seq >= l0seq { - l0found = true - l0seq = seq - l0vt = vt - l0val = val_ + if fukey, fseq, fkt, fkerr := parseIkey(fikey); fkerr == nil { + if v.s.icmp.uCompare(ukey, fukey) == 0 { + if level == 0 { + if fseq >= zseq { + zfound = true + zseq = fseq + zkt = fkt + zval = fval + } + } else { + switch fkt { + case ktVal: + value = fval + err = nil + case ktDel: + default: + panic("leveldb: invalid iKey type") + } + return false } - } else { - switch vt { - case tVal: - value = val_ - err = nil - case tDel: - default: - panic("leveldb: invalid internal key type") - } - return false } } else { - err = errors.New("leveldb: internal key corrupted") + err = fkerr return false } return true }, func(level int) bool { - if l0found { - switch l0vt { - case tVal: - value = l0val + if zfound { + switch zkt { + case ktVal: + value = zval err = nil - case tDel: + case ktDel: default: - panic("leveldb: invalid internal key type") + panic("leveldb: invalid iKey type") } return false } @@ -216,13 +213,13 @@ func (v *version) getIterators(slice *util.Range, ro *opt.ReadOptions) (its []it its = append(its, it) } - strict := v.s.o.GetStrict(opt.StrictIterator) || ro.GetStrict(opt.StrictIterator) + strict := opt.GetStrict(v.s.o.Options, ro, opt.StrictReader) for _, tables := range v.tables[1:] { if len(tables) == 0 { continue } - it := iterator.NewIndexedIterator(tables.newIndexIterator(v.s.tops, v.s.icmp, slice, ro), strict, true) + it := iterator.NewIndexedIterator(tables.newIndexIterator(v.s.tops, v.s.icmp, slice, ro), strict) its = append(its, it) } @@ -230,7 +227,7 @@ func (v *version) getIterators(slice *util.Range, ro *opt.ReadOptions) (its []it } func (v *version) newStaging() *versionStaging { - return &versionStaging{base: v} + return &versionStaging{base: v, tables: make([]tablesScratch, v.s.o.GetNumLevel())} } // Spawn a new version based on this version. @@ -285,12 +282,13 @@ func (v *version) offsetOf(ikey iKey) (n uint64, err error) { func (v *version) pickLevel(umin, umax []byte) (level int) { if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) { var overlaps tFiles - for ; level < kMaxMemCompactLevel; level++ { + maxLevel := v.s.o.GetMaxMemCompationLevel() + for ; level < maxLevel; level++ { if v.tables[level+1].overlaps(v.s.icmp, umin, umax, false) { break } overlaps = v.tables[level+2].getOverlaps(overlaps, v.s.icmp, umin, umax, false) - if overlaps.size() > kMaxGrandParentOverlapBytes { + if overlaps.size() > uint64(v.s.o.GetCompactionGPOverlaps(level)) { break } } @@ -318,9 +316,9 @@ func (v *version) computeCompaction() { // file size is small (perhaps because of a small write-buffer // setting, or very high compression ratios, or lots of // overwrites/deletions). - score = float64(len(tables)) / kL0_CompactionTrigger + score = float64(len(tables)) / float64(v.s.o.GetCompactionL0Trigger()) } else { - score = float64(tables.size()) / levelMaxSize[level] + score = float64(tables.size()) / float64(v.s.o.GetCompactionTotalSize(level)) } if score > bestScore { @@ -337,12 +335,14 @@ func (v *version) needCompaction() bool { return v.cScore >= 1 || atomic.LoadPointer(&v.cSeek) != nil } +type tablesScratch struct { + added map[uint64]atRecord + deleted map[uint64]struct{} +} + type versionStaging struct { base *version - tables [kNumLevels]struct { - added map[uint64]ntRecord - deleted map[uint64]struct{} - } + tables []tablesScratch } func (p *versionStaging) commit(r *sessionRecord) { @@ -367,7 +367,7 @@ func (p *versionStaging) commit(r *sessionRecord) { tm := &(p.tables[r.level]) if tm.added == nil { - tm.added = make(map[uint64]ntRecord) + tm.added = make(map[uint64]atRecord) } tm.added[r.num] = r @@ -379,7 +379,7 @@ func (p *versionStaging) commit(r *sessionRecord) { func (p *versionStaging) finish() *version { // Build new version. - nv := &version{s: p.base.s} + nv := newVersion(p.base.s) for level, tm := range p.tables { btables := p.base.tables[level] @@ -402,7 +402,7 @@ func (p *versionStaging) finish() *version { // New tables. for _, r := range tm.added { - nt = append(nt, r.makeFile(p.base.s)) + nt = append(nt, p.base.s.tableFileFromRecord(r)) } // Sort tables. @@ -429,7 +429,7 @@ func (vr *versionReleaser) Release() { v := vr.v v.s.vmu.Lock() if !vr.once { - v.release_NB() + v.releaseNB() vr.once = true } v.s.vmu.Unlock() diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/.hgignore b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/.hgignore new file mode 100644 index 00000000..d804706f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/.hgignore @@ -0,0 +1,30 @@ +syntax:glob +.DS_Store +.git +.gitignore +*.[568ao] +*.ao +*.so +*.pyc +._* +.nfs.* +[568a].out +*~ +*.orig +*.rej +*.exe +.*.swp +core +*.cgo*.go +*.cgo*.c +_cgo_* +_obj +_test +_testmain.go +build.out +snappy/testdata +test.out +y.tab.[ch] + +syntax:regexp +^.*/core.[0-9]*$ diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/AUTHORS b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/AUTHORS new file mode 100644 index 00000000..8ddb5b7a --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/AUTHORS @@ -0,0 +1,12 @@ +# This is the official list of Snappy-Go authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# Please keep the list sorted. + +Google Inc. +Jan Mercl <0xjnml@gmail.com> diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/CONTRIBUTORS b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/CONTRIBUTORS new file mode 100644 index 00000000..50b69c80 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/CONTRIBUTORS @@ -0,0 +1,34 @@ +# This is the official list of people who can contribute +# (and typically have contributed) code to the Snappy-Go repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# The submission process automatically checks to make sure +# that people submitting code are listed in this file (by email address). +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# http://code.google.com/legal/individual-cla-v1.0.html +# http://code.google.com/legal/corporate-cla-v1.0.html +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +# Name + +# Please keep the list sorted. + +Jan Mercl <0xjnml@gmail.com> +Kai Backman +Marc-Antoine Ruel +Nigel Tao +Rob Pike +Russ Cox diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/LICENSE b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/LICENSE new file mode 100644 index 00000000..6050c10f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/README b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/README new file mode 100644 index 00000000..3cf8be1f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/README @@ -0,0 +1,11 @@ +This is a Snappy library for the Go programming language. + +To download and install from source: +$ go get code.google.com/p/snappy-go/snappy + +Unless otherwise noted, the Snappy-Go source files are distributed +under the BSD-style license found in the LICENSE file. + +Contributions should follow the same procedure as for the Go project: +http://golang.org/doc/contribute.html + diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/lib/codereview/codereview.cfg b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/lib/codereview/codereview.cfg new file mode 100644 index 00000000..93b55c0a --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/lib/codereview/codereview.cfg @@ -0,0 +1 @@ +defaultcc: golang-dev@googlegroups.com diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/decode.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/decode.go new file mode 100644 index 00000000..d93c1b9d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/decode.go @@ -0,0 +1,124 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "encoding/binary" + "errors" +) + +// ErrCorrupt reports that the input is invalid. +var ErrCorrupt = errors.New("snappy: corrupt input") + +// DecodedLen returns the length of the decoded block. +func DecodedLen(src []byte) (int, error) { + v, _, err := decodedLen(src) + return v, err +} + +// decodedLen returns the length of the decoded block and the number of bytes +// that the length header occupied. +func decodedLen(src []byte) (blockLen, headerLen int, err error) { + v, n := binary.Uvarint(src) + if n == 0 { + return 0, 0, ErrCorrupt + } + if uint64(int(v)) != v { + return 0, 0, errors.New("snappy: decoded block is too large") + } + return int(v), n, nil +} + +// Decode returns the decoded form of src. The returned slice may be a sub- +// slice of dst if dst was large enough to hold the entire decoded block. +// Otherwise, a newly allocated slice will be returned. +// It is valid to pass a nil dst. +func Decode(dst, src []byte) ([]byte, error) { + dLen, s, err := decodedLen(src) + if err != nil { + return nil, err + } + if len(dst) < dLen { + dst = make([]byte, dLen) + } + + var d, offset, length int + for s < len(src) { + switch src[s] & 0x03 { + case tagLiteral: + x := uint(src[s] >> 2) + switch { + case x < 60: + s += 1 + case x == 60: + s += 2 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-1]) + case x == 61: + s += 3 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-2]) | uint(src[s-1])<<8 + case x == 62: + s += 4 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16 + case x == 63: + s += 5 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24 + } + length = int(x + 1) + if length <= 0 { + return nil, errors.New("snappy: unsupported literal length") + } + if length > len(dst)-d || length > len(src)-s { + return nil, ErrCorrupt + } + copy(dst[d:], src[s:s+length]) + d += length + s += length + continue + + case tagCopy1: + s += 2 + if s > len(src) { + return nil, ErrCorrupt + } + length = 4 + int(src[s-2])>>2&0x7 + offset = int(src[s-2])&0xe0<<3 | int(src[s-1]) + + case tagCopy2: + s += 3 + if s > len(src) { + return nil, ErrCorrupt + } + length = 1 + int(src[s-3])>>2 + offset = int(src[s-2]) | int(src[s-1])<<8 + + case tagCopy4: + return nil, errors.New("snappy: unsupported COPY_4 tag") + } + + end := d + length + if offset > d || end > len(dst) { + return nil, ErrCorrupt + } + for ; d < end; d++ { + dst[d] = dst[d-offset] + } + } + if d != dLen { + return nil, ErrCorrupt + } + return dst[:d], nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/encode.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/encode.go new file mode 100644 index 00000000..b2371db1 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/encode.go @@ -0,0 +1,174 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "encoding/binary" +) + +// We limit how far copy back-references can go, the same as the C++ code. +const maxOffset = 1 << 15 + +// emitLiteral writes a literal chunk and returns the number of bytes written. +func emitLiteral(dst, lit []byte) int { + i, n := 0, uint(len(lit)-1) + switch { + case n < 60: + dst[0] = uint8(n)<<2 | tagLiteral + i = 1 + case n < 1<<8: + dst[0] = 60<<2 | tagLiteral + dst[1] = uint8(n) + i = 2 + case n < 1<<16: + dst[0] = 61<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + i = 3 + case n < 1<<24: + dst[0] = 62<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + dst[3] = uint8(n >> 16) + i = 4 + case int64(n) < 1<<32: + dst[0] = 63<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + dst[3] = uint8(n >> 16) + dst[4] = uint8(n >> 24) + i = 5 + default: + panic("snappy: source buffer is too long") + } + if copy(dst[i:], lit) != len(lit) { + panic("snappy: destination buffer is too short") + } + return i + len(lit) +} + +// emitCopy writes a copy chunk and returns the number of bytes written. +func emitCopy(dst []byte, offset, length int) int { + i := 0 + for length > 0 { + x := length - 4 + if 0 <= x && x < 1<<3 && offset < 1<<11 { + dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1 + dst[i+1] = uint8(offset) + i += 2 + break + } + + x = length + if x > 1<<6 { + x = 1 << 6 + } + dst[i+0] = uint8(x-1)<<2 | tagCopy2 + dst[i+1] = uint8(offset) + dst[i+2] = uint8(offset >> 8) + i += 3 + length -= x + } + return i +} + +// Encode returns the encoded form of src. The returned slice may be a sub- +// slice of dst if dst was large enough to hold the entire encoded block. +// Otherwise, a newly allocated slice will be returned. +// It is valid to pass a nil dst. +func Encode(dst, src []byte) ([]byte, error) { + if n := MaxEncodedLen(len(src)); len(dst) < n { + dst = make([]byte, n) + } + + // The block starts with the varint-encoded length of the decompressed bytes. + d := binary.PutUvarint(dst, uint64(len(src))) + + // Return early if src is short. + if len(src) <= 4 { + if len(src) != 0 { + d += emitLiteral(dst[d:], src) + } + return dst[:d], nil + } + + // Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive. + const maxTableSize = 1 << 14 + shift, tableSize := uint(32-8), 1<<8 + for tableSize < maxTableSize && tableSize < len(src) { + shift-- + tableSize *= 2 + } + var table [maxTableSize]int + + // Iterate over the source bytes. + var ( + s int // The iterator position. + t int // The last position with the same hash as s. + lit int // The start position of any pending literal bytes. + ) + for s+3 < len(src) { + // Update the hash table. + b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3] + h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24 + p := &table[(h*0x1e35a7bd)>>shift] + // We need to to store values in [-1, inf) in table. To save + // some initialization time, (re)use the table's zero value + // and shift the values against this zero: add 1 on writes, + // subtract 1 on reads. + t, *p = *p-1, s+1 + // If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte. + if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] { + s++ + continue + } + // Otherwise, we have a match. First, emit any pending literal bytes. + if lit != s { + d += emitLiteral(dst[d:], src[lit:s]) + } + // Extend the match to be as long as possible. + s0 := s + s, t = s+4, t+4 + for s < len(src) && src[s] == src[t] { + s++ + t++ + } + // Emit the copied bytes. + d += emitCopy(dst[d:], s-t, s-s0) + lit = s + } + + // Emit any final pending literal bytes and return. + if lit != len(src) { + d += emitLiteral(dst[d:], src[lit:]) + } + return dst[:d], nil +} + +// MaxEncodedLen returns the maximum length of a snappy block, given its +// uncompressed length. +func MaxEncodedLen(srcLen int) int { + // Compressed data can be defined as: + // compressed := item* literal* + // item := literal* copy + // + // The trailing literal sequence has a space blowup of at most 62/60 + // since a literal of length 60 needs one tag byte + one extra byte + // for length information. + // + // Item blowup is trickier to measure. Suppose the "copy" op copies + // 4 bytes of data. Because of a special check in the encoding code, + // we produce a 4-byte copy only if the offset is < 65536. Therefore + // the copy op takes 3 bytes to encode, and this type of item leads + // to at most the 62/60 blowup for representing literals. + // + // Suppose the "copy" op copies 5 bytes of data. If the offset is big + // enough, it will take 5 bytes to encode the copy op. Therefore the + // worst case here is a one-byte literal followed by a five-byte copy. + // That is, 6 bytes of input turn into 7 bytes of "compressed" data. + // + // This last factor dominates the blowup, so the final estimate is: + return 32 + srcLen + srcLen/6 +} diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy.go new file mode 100644 index 00000000..2f1b790d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy.go @@ -0,0 +1,38 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package snappy implements the snappy block-based compression format. +// It aims for very high speeds and reasonable compression. +// +// The C++ snappy implementation is at http://code.google.com/p/snappy/ +package snappy + +/* +Each encoded block begins with the varint-encoded length of the decoded data, +followed by a sequence of chunks. Chunks begin and end on byte boundaries. The +first byte of each chunk is broken into its 2 least and 6 most significant bits +called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag. +Zero means a literal tag. All other values mean a copy tag. + +For literal tags: + - If m < 60, the next 1 + m bytes are literal bytes. + - Otherwise, let n be the little-endian unsigned integer denoted by the next + m - 59 bytes. The next 1 + n bytes after that are literal bytes. + +For copy tags, length bytes are copied from offset bytes ago, in the style of +Lempel-Ziv compression algorithms. In particular: + - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12). + The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10 + of the offset. The next byte is bits 0-7 of the offset. + - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65). + The length is 1 + m. The offset is the little-endian unsigned integer + denoted by the next 2 bytes. + - For l == 3, this tag is a legacy format that is no longer supported. +*/ +const ( + tagLiteral = 0x00 + tagCopy1 = 0x01 + tagCopy2 = 0x02 + tagCopy4 = 0x03 +) diff --git a/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy_test.go b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy_test.go new file mode 100644 index 00000000..7ba83924 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/github.com/syndtr/gosnappy/snappy/snappy_test.go @@ -0,0 +1,261 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "os" + "path/filepath" + "strings" + "testing" +) + +var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks") + +func roundtrip(b, ebuf, dbuf []byte) error { + e, err := Encode(ebuf, b) + if err != nil { + return fmt.Errorf("encoding error: %v", err) + } + d, err := Decode(dbuf, e) + if err != nil { + return fmt.Errorf("decoding error: %v", err) + } + if !bytes.Equal(b, d) { + return fmt.Errorf("roundtrip mismatch:\n\twant %v\n\tgot %v", b, d) + } + return nil +} + +func TestEmpty(t *testing.T) { + if err := roundtrip(nil, nil, nil); err != nil { + t.Fatal(err) + } +} + +func TestSmallCopy(t *testing.T) { + for _, ebuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} { + for _, dbuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} { + for i := 0; i < 32; i++ { + s := "aaaa" + strings.Repeat("b", i) + "aaaabbbb" + if err := roundtrip([]byte(s), ebuf, dbuf); err != nil { + t.Errorf("len(ebuf)=%d, len(dbuf)=%d, i=%d: %v", len(ebuf), len(dbuf), i, err) + } + } + } + } +} + +func TestSmallRand(t *testing.T) { + rand.Seed(27354294) + for n := 1; n < 20000; n += 23 { + b := make([]byte, n) + for i, _ := range b { + b[i] = uint8(rand.Uint32()) + } + if err := roundtrip(b, nil, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestSmallRegular(t *testing.T) { + for n := 1; n < 20000; n += 23 { + b := make([]byte, n) + for i, _ := range b { + b[i] = uint8(i%10 + 'a') + } + if err := roundtrip(b, nil, nil); err != nil { + t.Fatal(err) + } + } +} + +func benchDecode(b *testing.B, src []byte) { + encoded, err := Encode(nil, src) + if err != nil { + b.Fatal(err) + } + // Bandwidth is in amount of uncompressed data. + b.SetBytes(int64(len(src))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Decode(src, encoded) + } +} + +func benchEncode(b *testing.B, src []byte) { + // Bandwidth is in amount of uncompressed data. + b.SetBytes(int64(len(src))) + dst := make([]byte, MaxEncodedLen(len(src))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Encode(dst, src) + } +} + +func readFile(b *testing.B, filename string) []byte { + src, err := ioutil.ReadFile(filename) + if err != nil { + b.Fatalf("failed reading %s: %s", filename, err) + } + if len(src) == 0 { + b.Fatalf("%s has zero length", filename) + } + return src +} + +// expand returns a slice of length n containing repeated copies of src. +func expand(src []byte, n int) []byte { + dst := make([]byte, n) + for x := dst; len(x) > 0; { + i := copy(x, src) + x = x[i:] + } + return dst +} + +func benchWords(b *testing.B, n int, decode bool) { + // Note: the file is OS-language dependent so the resulting values are not + // directly comparable for non-US-English OS installations. + data := expand(readFile(b, "/usr/share/dict/words"), n) + if decode { + benchDecode(b, data) + } else { + benchEncode(b, data) + } +} + +func BenchmarkWordsDecode1e3(b *testing.B) { benchWords(b, 1e3, true) } +func BenchmarkWordsDecode1e4(b *testing.B) { benchWords(b, 1e4, true) } +func BenchmarkWordsDecode1e5(b *testing.B) { benchWords(b, 1e5, true) } +func BenchmarkWordsDecode1e6(b *testing.B) { benchWords(b, 1e6, true) } +func BenchmarkWordsEncode1e3(b *testing.B) { benchWords(b, 1e3, false) } +func BenchmarkWordsEncode1e4(b *testing.B) { benchWords(b, 1e4, false) } +func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) } +func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) } + +// testFiles' values are copied directly from +// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc. +// The label field is unused in snappy-go. +var testFiles = []struct { + label string + filename string +}{ + {"html", "html"}, + {"urls", "urls.10K"}, + {"jpg", "house.jpg"}, + {"pdf", "mapreduce-osdi-1.pdf"}, + {"html4", "html_x_4"}, + {"cp", "cp.html"}, + {"c", "fields.c"}, + {"lsp", "grammar.lsp"}, + {"xls", "kennedy.xls"}, + {"txt1", "alice29.txt"}, + {"txt2", "asyoulik.txt"}, + {"txt3", "lcet10.txt"}, + {"txt4", "plrabn12.txt"}, + {"bin", "ptt5"}, + {"sum", "sum"}, + {"man", "xargs.1"}, + {"pb", "geo.protodata"}, + {"gaviota", "kppkn.gtb"}, +} + +// The test data files are present at this canonical URL. +const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/" + +func downloadTestdata(basename string) (errRet error) { + filename := filepath.Join("testdata", basename) + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create %s: %s", filename, err) + } + defer f.Close() + defer func() { + if errRet != nil { + os.Remove(filename) + } + }() + resp, err := http.Get(baseURL + basename) + if err != nil { + return fmt.Errorf("failed to download %s: %s", baseURL+basename, err) + } + defer resp.Body.Close() + _, err = io.Copy(f, resp.Body) + if err != nil { + return fmt.Errorf("failed to write %s: %s", filename, err) + } + return nil +} + +func benchFile(b *testing.B, n int, decode bool) { + filename := filepath.Join("testdata", testFiles[n].filename) + if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 { + if !*download { + b.Fatal("test data not found; skipping benchmark without the -download flag") + } + // Download the official snappy C++ implementation reference test data + // files for benchmarking. + if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) { + b.Fatalf("failed to create testdata: %s", err) + } + for _, tf := range testFiles { + if err := downloadTestdata(tf.filename); err != nil { + b.Fatalf("failed to download testdata: %s", err) + } + } + } + data := readFile(b, filename) + if decode { + benchDecode(b, data) + } else { + benchEncode(b, data) + } +} + +// Naming convention is kept similar to what snappy's C++ implementation uses. +func Benchmark_UFlat0(b *testing.B) { benchFile(b, 0, true) } +func Benchmark_UFlat1(b *testing.B) { benchFile(b, 1, true) } +func Benchmark_UFlat2(b *testing.B) { benchFile(b, 2, true) } +func Benchmark_UFlat3(b *testing.B) { benchFile(b, 3, true) } +func Benchmark_UFlat4(b *testing.B) { benchFile(b, 4, true) } +func Benchmark_UFlat5(b *testing.B) { benchFile(b, 5, true) } +func Benchmark_UFlat6(b *testing.B) { benchFile(b, 6, true) } +func Benchmark_UFlat7(b *testing.B) { benchFile(b, 7, true) } +func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) } +func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) } +func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) } +func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) } +func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) } +func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) } +func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) } +func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) } +func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) } +func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) } +func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) } +func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) } +func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) } +func Benchmark_ZFlat3(b *testing.B) { benchFile(b, 3, false) } +func Benchmark_ZFlat4(b *testing.B) { benchFile(b, 4, false) } +func Benchmark_ZFlat5(b *testing.B) { benchFile(b, 5, false) } +func Benchmark_ZFlat6(b *testing.B) { benchFile(b, 6, false) } +func Benchmark_ZFlat7(b *testing.B) { benchFile(b, 7, false) } +func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) } +func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) } +func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) } +func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) } +func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) } +func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) } +func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) } +func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) } +func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) } +func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/.hgignore b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/.gitignore similarity index 87% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/.hgignore rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/.gitignore index c27ee1ef..8339fd61 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/.hgignore +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/.gitignore @@ -1,3 +1,2 @@ # Add no patterns to .hgignore except for files generated by the build. -syntax:glob last-change diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/AUTHORS b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/AUTHORS similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/AUTHORS rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/AUTHORS diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/CONTRIBUTORS b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/CONTRIBUTORS similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/CONTRIBUTORS rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/CONTRIBUTORS diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/LICENSE b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/LICENSE similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/LICENSE rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/LICENSE diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/PATENTS b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/PATENTS similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/PATENTS rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/PATENTS diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/README b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/README similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/README rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/README diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/base64.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/base64.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/base64.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/base64.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt.go index 10b8d64f..f8b807f9 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt.go @@ -4,15 +4,15 @@ // Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf -package bcrypt +package bcrypt // import "golang.org/x/crypto/bcrypt" // The code is a port of Provos and Mazières's C implementation. import ( - "code.google.com/p/go.crypto/blowfish" "crypto/rand" "crypto/subtle" "errors" "fmt" + "golang.org/x/crypto/blowfish" "io" "strconv" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt_test.go similarity index 96% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt_test.go index f9491031..f08a6f5b 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bcrypt/bcrypt_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bcrypt/bcrypt_test.go @@ -53,6 +53,15 @@ func TestBcryptingIsCorrect(t *testing.T) { } } +func TestVeryShortPasswords(t *testing.T) { + key := []byte("k") + salt := []byte("XajjQvNhvvRt5GSeFk1xFe") + _, err := bcrypt(key, 10, salt) + if err != nil { + t.Errorf("One byte key resulted in error: %s", err) + } +} + func TestTooLongPasswordsWork(t *testing.T) { salt := []byte("XajjQvNhvvRt5GSeFk1xFe") // One byte over the usual 56 byte limit that blowfish has diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/block.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/block.go similarity index 81% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/block.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/block.go index 7b14dec7..9d80f195 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/block.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/block.go @@ -4,6 +4,22 @@ package blowfish +// getNextWord returns the next big-endian uint32 value from the byte slice +// at the given position in a circular manner, updating the position. +func getNextWord(b []byte, pos *int) uint32 { + var w uint32 + j := *pos + for i := 0; i < 4; i++ { + w = w<<8 | uint32(b[j]) + j++ + if j >= len(b) { + j = 0 + } + } + *pos = j + return w +} + // ExpandKey performs a key expansion on the given *Cipher. Specifically, it // performs the Blowfish algorithm's key schedule which sets up the *Cipher's // pi and substitution tables for calls to Encrypt. This is used, primarily, @@ -12,6 +28,7 @@ package blowfish func ExpandKey(key []byte, c *Cipher) { j := 0 for i := 0; i < 18; i++ { + // Using inlined getNextWord for performance. var d uint32 for k := 0; k < 4; k++ { d = d<<8 | uint32(key[j]) @@ -54,86 +71,44 @@ func ExpandKey(key []byte, c *Cipher) { func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { j := 0 for i := 0; i < 18; i++ { - var d uint32 - for k := 0; k < 4; k++ { - d = d<<8 | uint32(key[j]) - j++ - if j >= len(key) { - j = 0 - } - } - c.p[i] ^= d + c.p[i] ^= getNextWord(key, &j) } j = 0 - var expandedSalt [4]uint32 - for i := range expandedSalt { - var d uint32 - for k := 0; k < 4; k++ { - d = d<<8 | uint32(salt[j]) - j++ - if j >= len(salt) { - j = 0 - } - } - expandedSalt[i] = d - } - var l, r uint32 for i := 0; i < 18; i += 2 { - l ^= expandedSalt[i&2] - r ^= expandedSalt[(i&2)+1] + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) l, r = encryptBlock(l, r, c) c.p[i], c.p[i+1] = l, r } - for i := 0; i < 256; i += 4 { - l ^= expandedSalt[2] - r ^= expandedSalt[3] + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) l, r = encryptBlock(l, r, c) c.s0[i], c.s0[i+1] = l, r - - l ^= expandedSalt[0] - r ^= expandedSalt[1] - l, r = encryptBlock(l, r, c) - c.s0[i+2], c.s0[i+3] = l, r - } - for i := 0; i < 256; i += 4 { - l ^= expandedSalt[2] - r ^= expandedSalt[3] + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) l, r = encryptBlock(l, r, c) c.s1[i], c.s1[i+1] = l, r - - l ^= expandedSalt[0] - r ^= expandedSalt[1] - l, r = encryptBlock(l, r, c) - c.s1[i+2], c.s1[i+3] = l, r } - for i := 0; i < 256; i += 4 { - l ^= expandedSalt[2] - r ^= expandedSalt[3] + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) l, r = encryptBlock(l, r, c) c.s2[i], c.s2[i+1] = l, r - - l ^= expandedSalt[0] - r ^= expandedSalt[1] - l, r = encryptBlock(l, r, c) - c.s2[i+2], c.s2[i+3] = l, r } - for i := 0; i < 256; i += 4 { - l ^= expandedSalt[2] - r ^= expandedSalt[3] + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) l, r = encryptBlock(l, r, c) c.s3[i], c.s3[i+1] = l, r - - l ^= expandedSalt[0] - r ^= expandedSalt[1] - l, r = encryptBlock(l, r, c) - c.s3[i+2], c.s3[i+3] = l, r } } @@ -182,9 +157,3 @@ func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { xr ^= c.p[0] return xr, xl } - -func zero(x []uint32) { - for i := range x { - x[i] = 0 - } -} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/blowfish_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/blowfish_test.go similarity index 73% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/blowfish_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/blowfish_test.go index 1038d2e3..7afa1fdf 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/blowfish_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/blowfish_test.go @@ -4,9 +4,7 @@ package blowfish -import ( - "testing" -) +import "testing" type CryptTest struct { key []byte @@ -192,19 +190,85 @@ func TestCipherDecrypt(t *testing.T) { } func TestSaltedCipherKeyLength(t *testing.T) { - var key []byte - for i := 0; i < 4; i++ { - _, err := NewSaltedCipher(key, []byte{'a'}) - if err != KeySizeError(i) { - t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(i)) - } - key = append(key, 'a') + if _, err := NewSaltedCipher(nil, []byte{'a'}); err != KeySizeError(0) { + t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(0)) } // A 57-byte key. One over the typical blowfish restriction. - key = []byte("012345678901234567890123456789012345678901234567890123456") - _, err := NewSaltedCipher(key, []byte{'a'}) - if err != nil { + key := []byte("012345678901234567890123456789012345678901234567890123456") + if _, err := NewSaltedCipher(key, []byte{'a'}); err != nil { t.Errorf("NewSaltedCipher with long key, gave error %#v", err) } } + +// Test vectors generated with Blowfish from OpenSSH. +var saltedVectors = [][8]byte{ + {0x0c, 0x82, 0x3b, 0x7b, 0x8d, 0x01, 0x4b, 0x7e}, + {0xd1, 0xe1, 0x93, 0xf0, 0x70, 0xa6, 0xdb, 0x12}, + {0xfc, 0x5e, 0xba, 0xde, 0xcb, 0xf8, 0x59, 0xad}, + {0x8a, 0x0c, 0x76, 0xe7, 0xdd, 0x2c, 0xd3, 0xa8}, + {0x2c, 0xcb, 0x7b, 0xee, 0xac, 0x7b, 0x7f, 0xf8}, + {0xbb, 0xf6, 0x30, 0x6f, 0xe1, 0x5d, 0x62, 0xbf}, + {0x97, 0x1e, 0xc1, 0x3d, 0x3d, 0xe0, 0x11, 0xe9}, + {0x06, 0xd7, 0x4d, 0xb1, 0x80, 0xa3, 0xb1, 0x38}, + {0x67, 0xa1, 0xa9, 0x75, 0x0e, 0x5b, 0xc6, 0xb4}, + {0x51, 0x0f, 0x33, 0x0e, 0x4f, 0x67, 0xd2, 0x0c}, + {0xf1, 0x73, 0x7e, 0xd8, 0x44, 0xea, 0xdb, 0xe5}, + {0x14, 0x0e, 0x16, 0xce, 0x7f, 0x4a, 0x9c, 0x7b}, + {0x4b, 0xfe, 0x43, 0xfd, 0xbf, 0x36, 0x04, 0x47}, + {0xb1, 0xeb, 0x3e, 0x15, 0x36, 0xa7, 0xbb, 0xe2}, + {0x6d, 0x0b, 0x41, 0xdd, 0x00, 0x98, 0x0b, 0x19}, + {0xd3, 0xce, 0x45, 0xce, 0x1d, 0x56, 0xb7, 0xfc}, + {0xd9, 0xf0, 0xfd, 0xda, 0xc0, 0x23, 0xb7, 0x93}, + {0x4c, 0x6f, 0xa1, 0xe4, 0x0c, 0xa8, 0xca, 0x57}, + {0xe6, 0x2f, 0x28, 0xa7, 0x0c, 0x94, 0x0d, 0x08}, + {0x8f, 0xe3, 0xf0, 0xb6, 0x29, 0xe3, 0x44, 0x03}, + {0xff, 0x98, 0xdd, 0x04, 0x45, 0xb4, 0x6d, 0x1f}, + {0x9e, 0x45, 0x4d, 0x18, 0x40, 0x53, 0xdb, 0xef}, + {0xb7, 0x3b, 0xef, 0x29, 0xbe, 0xa8, 0x13, 0x71}, + {0x02, 0x54, 0x55, 0x41, 0x8e, 0x04, 0xfc, 0xad}, + {0x6a, 0x0a, 0xee, 0x7c, 0x10, 0xd9, 0x19, 0xfe}, + {0x0a, 0x22, 0xd9, 0x41, 0xcc, 0x23, 0x87, 0x13}, + {0x6e, 0xff, 0x1f, 0xff, 0x36, 0x17, 0x9c, 0xbe}, + {0x79, 0xad, 0xb7, 0x40, 0xf4, 0x9f, 0x51, 0xa6}, + {0x97, 0x81, 0x99, 0xa4, 0xde, 0x9e, 0x9f, 0xb6}, + {0x12, 0x19, 0x7a, 0x28, 0xd0, 0xdc, 0xcc, 0x92}, + {0x81, 0xda, 0x60, 0x1e, 0x0e, 0xdd, 0x65, 0x56}, + {0x7d, 0x76, 0x20, 0xb2, 0x73, 0xc9, 0x9e, 0xee}, +} + +func TestSaltedCipher(t *testing.T) { + var key, salt [32]byte + for i := range key { + key[i] = byte(i) + salt[i] = byte(i + 32) + } + for i, v := range saltedVectors { + c, err := NewSaltedCipher(key[:], salt[:i]) + if err != nil { + t.Fatal(err) + } + var buf [8]byte + c.Encrypt(buf[:], buf[:]) + if v != buf { + t.Errorf("%d: expected %x, got %x", i, v, buf) + } + } +} + +func BenchmarkExpandKeyWithSalt(b *testing.B) { + key := make([]byte, 32) + salt := make([]byte, 16) + c, _ := NewCipher(key) + for i := 0; i < b.N; i++ { + expandKeyWithSalt(key, salt, c) + } +} + +func BenchmarkExpandKey(b *testing.B) { + key := make([]byte, 32) + c, _ := NewCipher(key) + for i := 0; i < b.N; i++ { + ExpandKey(key, c) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/cipher.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/cipher.go similarity index 89% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/cipher.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/cipher.go index fbefe78c..542984aa 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/cipher.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/cipher.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. -package blowfish +package blowfish // import "golang.org/x/crypto/blowfish" // The code is a port of Bruce Schneier's C implementation. // See http://www.schneier.com/blowfish.html. @@ -26,14 +26,13 @@ func (k KeySizeError) Error() string { } // NewCipher creates and returns a Cipher. -// The key argument should be the Blowfish key, 4 to 56 bytes. +// The key argument should be the Blowfish key, from 1 to 56 bytes. func NewCipher(key []byte) (*Cipher, error) { var result Cipher - k := len(key) - if k < 4 || k > 56 { + if k := len(key); k < 1 || k > 56 { return nil, KeySizeError(k) } - initCipher(key, &result) + initCipher(&result) ExpandKey(key, &result) return &result, nil } @@ -41,14 +40,16 @@ func NewCipher(key []byte) (*Cipher, error) { // NewSaltedCipher creates a returns a Cipher that folds a salt into its key // schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is // sufficient and desirable. For bcrypt compatiblity, the key can be over 56 -// bytes. Only the first 16 bytes of salt are used. +// bytes. func NewSaltedCipher(key, salt []byte) (*Cipher, error) { + if len(salt) == 0 { + return NewCipher(key) + } var result Cipher - k := len(key) - if k < 4 { + if k := len(key); k < 1 { return nil, KeySizeError(k) } - initCipher(key, &result) + initCipher(&result) expandKeyWithSalt(key, salt, &result) return &result, nil } @@ -81,7 +82,7 @@ func (c *Cipher) Decrypt(dst, src []byte) { dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) } -func initCipher(key []byte, c *Cipher) { +func initCipher(c *Cipher) { copy(c.p[0:], p[0:]) copy(c.s0[0:], s0[0:]) copy(c.s1[0:], s1[0:]) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/const.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/const.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/blowfish/const.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/blowfish/const.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/bn256.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/bn256.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/bn256.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/bn256.go index bc96e3d3..014f8b35 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/bn256.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/bn256.go @@ -14,7 +14,7 @@ // Barreto-Naehrig curve as described in // http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible // with the implementation described in that paper. -package bn256 +package bn256 // import "golang.org/x/crypto/bn256" import ( "crypto/rand" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/bn256_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/bn256_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/bn256_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/bn256_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/constants.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/constants.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/constants.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/constants.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/curve.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/curve.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/curve.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/curve.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/example_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/example_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/example_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/example_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp12.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp12.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp12.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp12.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp2.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp2.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp2.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp2.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp6.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp6.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/gfp6.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/gfp6.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/optate.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/optate.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/optate.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/optate.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/twist.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/twist.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/bn256/twist.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/bn256/twist.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/cast5/cast5.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/cast5/cast5.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/cast5/cast5.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/cast5/cast5.go index 8c1b299b..0b4af37b 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/cast5/cast5.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/cast5/cast5.go @@ -4,7 +4,7 @@ // Package cast5 implements CAST5, as defined in RFC 2144. CAST5 is a common // OpenPGP cipher. -package cast5 +package cast5 // import "golang.org/x/crypto/cast5" import "errors" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/cast5/cast5_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/cast5/cast5_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/cast5/cast5_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/cast5/cast5_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/const_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/const_amd64.s similarity index 81% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/const_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/const_amd64.s index cc420879..0517fc19 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/const_amd64.s +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/const_amd64.s @@ -8,13 +8,13 @@ // +build amd64,!gccgo DATA ·REDMASK51(SB)/8, $0x0007FFFFFFFFFFFF -GLOBL ·REDMASK51(SB), $8 +GLOBL ·REDMASK51(SB), 8, $8 DATA ·_121666_213(SB)/8, $996687872 -GLOBL ·_121666_213(SB), $8 +GLOBL ·_121666_213(SB), 8, $8 DATA ·_2P0(SB)/8, $0xFFFFFFFFFFFDA -GLOBL ·_2P0(SB), $8 +GLOBL ·_2P0(SB), 8, $8 DATA ·_2P1234(SB)/8, $0xFFFFFFFFFFFFE -GLOBL ·_2P1234(SB), $8 +GLOBL ·_2P1234(SB), 8, $8 diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/cswap_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/cswap_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/cswap_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/cswap_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/curve25519.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/curve25519.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/curve25519.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/curve25519.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/curve25519_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/curve25519_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/curve25519_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/curve25519_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/doc.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/doc.go similarity index 94% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/doc.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/doc.go index f7db9c1c..ebeea3c2 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/doc.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/doc.go @@ -4,7 +4,7 @@ // Package curve25519 provides an implementation of scalar multiplication on // the elliptic curve known as curve25519. See http://cr.yp.to/ecdh.html -package curve25519 +package curve25519 // import "golang.org/x/crypto/curve25519" // basePoint is the x coordinate of the generator of the curve. var basePoint = [32]byte{9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/freeze_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/freeze_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/freeze_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/freeze_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/ladderstep_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/ladderstep_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/ladderstep_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/ladderstep_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/mont25519_amd64.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/mont25519_amd64.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/mont25519_amd64.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/mont25519_amd64.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/mul_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/mul_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/mul_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/mul_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/square_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/square_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/curve25519/square_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/curve25519/square_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/example_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/example_test.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/example_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/example_test.go index 4eda6b2d..df843951 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/example_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/example_test.go @@ -6,10 +6,10 @@ package hkdf_test import ( "bytes" - "code.google.com/p/go.crypto/hkdf" "crypto/rand" "crypto/sha256" "fmt" + "golang.org/x/crypto/hkdf" "io" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/hkdf.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/hkdf.go similarity index 92% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/hkdf.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/hkdf.go index b20e834d..5bc24635 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/hkdf.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/hkdf.go @@ -10,7 +10,7 @@ // strong secret keys. // // RFC 5869: https://tools.ietf.org/html/rfc5869 -package hkdf +package hkdf // import "golang.org/x/crypto/hkdf" import ( "crypto/hmac" @@ -42,13 +42,11 @@ func (f *hkdf) Read(p []byte) (int, error) { p = p[n:] // Fill the buffer - var input []byte for len(p) > 0 { - input = append(f.prev, f.info...) - input = append(input, f.counter) - f.expander.Reset() - f.expander.Write(input) + f.expander.Write(f.prev) + f.expander.Write(f.info) + f.expander.Write([]byte{f.counter}) f.prev = f.expander.Sum(f.prev[:0]) f.counter++ diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/hkdf_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/hkdf_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/hkdf/hkdf_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/hkdf/hkdf_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4.go index c5f7c57d..6d9ba9e5 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package md4 implements the MD4 hash algorithm as defined in RFC 1320. -package md4 +package md4 // import "golang.org/x/crypto/md4" import ( "crypto" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4block.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4block.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/md4/md4block.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/md4/md4block.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box.go index a1e58e92..ca48a6db 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box.go @@ -15,12 +15,12 @@ negligible risk of collision. This package is interoperable with NaCl: http://nacl.cr.yp.to/box.html. */ -package box +package box // import "golang.org/x/crypto/nacl/box" import ( - "code.google.com/p/go.crypto/curve25519" - "code.google.com/p/go.crypto/nacl/secretbox" - "code.google.com/p/go.crypto/salsa20/salsa" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/secretbox" + "golang.org/x/crypto/salsa20/salsa" "io" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box_test.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box_test.go index 6cf2e15b..481ade28 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/box/box_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/box/box_test.go @@ -10,7 +10,7 @@ import ( "encoding/hex" "testing" - "code.google.com/p/go.crypto/curve25519" + "golang.org/x/crypto/curve25519" ) func TestSealOpen(t *testing.T) { diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/secretbox/secretbox.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/secretbox/secretbox.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/secretbox/secretbox.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/secretbox/secretbox.go index ad64c24e..dbf31bbf 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/secretbox/secretbox.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/secretbox/secretbox.go @@ -15,11 +15,11 @@ negligible risk of collision. This package is interoperable with NaCl: http://nacl.cr.yp.to/secretbox.html. */ -package secretbox +package secretbox // import "golang.org/x/crypto/nacl/secretbox" import ( - "code.google.com/p/go.crypto/poly1305" - "code.google.com/p/go.crypto/salsa20/salsa" + "golang.org/x/crypto/poly1305" + "golang.org/x/crypto/salsa20/salsa" ) // Overhead is the number of bytes of overhead when boxing a message. diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/secretbox/secretbox_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/secretbox/secretbox_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/nacl/secretbox/secretbox_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/nacl/secretbox/secretbox_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ocsp/ocsp.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ocsp/ocsp.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ocsp/ocsp.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ocsp/ocsp.go index 2a56a810..0252b583 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ocsp/ocsp.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ocsp/ocsp.go @@ -5,7 +5,7 @@ // Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses // are signed messages attesting to the validity of a certificate for a small // period of time. This is used to manage revocation for X.509 certificates. -package ocsp +package ocsp // import "golang.org/x/crypto/ocsp" import ( "crypto" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ocsp/ocsp_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ocsp/ocsp_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ocsp/ocsp_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ocsp/ocsp_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/armor.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/armor.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/armor.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/armor.go index c7ab2efd..592d1864 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/armor.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/armor.go @@ -4,13 +4,13 @@ // Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is // very similar to PEM except that it has an additional CRC checksum. -package armor +package armor // import "golang.org/x/crypto/openpgp/armor" import ( "bufio" "bytes" - "code.google.com/p/go.crypto/openpgp/errors" "encoding/base64" + "golang.org/x/crypto/openpgp/errors" "io" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/armor_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/armor_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/armor_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/armor_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/encode.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/encode.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/armor/encode.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/armor/encode.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/canonical_text.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/canonical_text.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/canonical_text.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/canonical_text.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/canonical_text_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/canonical_text_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/canonical_text_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/canonical_text_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go index a753bcf3..c2aac099 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go @@ -7,7 +7,7 @@ // // Clearsigned messages are cryptographically signed, but the contents of the // message are kept in plaintext so that it can be read without special tools. -package clearsign +package clearsign // import "golang.org/x/crypto/openpgp/clearsign" import ( "bufio" @@ -18,9 +18,9 @@ import ( "net/textproto" "strconv" - "code.google.com/p/go.crypto/openpgp/armor" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/packet" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" ) // A Block represents a clearsigned message. A signature on a Block can @@ -113,6 +113,7 @@ func Decode(data []byte) (b *Block, rest []byte) { b.Headers.Add(string(key), string(val)) } + firstLine := true for { start := rest @@ -126,9 +127,12 @@ func Decode(data []byte) (b *Block, rest []byte) { // The final CRLF isn't included in the hash so we don't write it until // we've seen the next line. - if len(b.Bytes) > 0 { + if firstLine { + firstLine = false + } else { b.Bytes = append(b.Bytes, crlf...) } + if bytes.HasPrefix(line, dashEscape) { line = line[2:] } @@ -296,10 +300,10 @@ func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) ( return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType))) } - h := hashType.New() - if h == nil { + if !hashType.Available() { return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType))) } + h := hashType.New() buffered := bufio.NewWriter(w) // start has a \n at the beginning that we don't want here. diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go similarity index 83% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go index 876d604d..51538b97 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/clearsign/clearsign_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go @@ -6,12 +6,12 @@ package clearsign import ( "bytes" - "code.google.com/p/go.crypto/openpgp" + "golang.org/x/crypto/openpgp" "testing" ) -func TestParse(t *testing.T) { - b, rest := Decode(clearsignInput) +func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) { + b, rest := Decode(input) if b == nil { t.Fatal("failed to decode clearsign message") } @@ -21,14 +21,12 @@ func TestParse(t *testing.T) { if b.ArmoredSignature.Type != "PGP SIGNATURE" { t.Errorf("bad armor type, got:%s, want:PGP SIGNATURE", b.ArmoredSignature.Type) } - expected := []byte("Hello world\r\nline 2") - if !bytes.Equal(b.Bytes, expected) { + if !bytes.Equal(b.Bytes, []byte(expected)) { t.Errorf("bad body, got:%x want:%x", b.Bytes, expected) } - expected = []byte("Hello world\nline 2\n") - if !bytes.Equal(b.Plaintext, expected) { - t.Errorf("bad plaintext, got:%x want:%x", b.Plaintext, expected) + if !bytes.Equal(b.Plaintext, []byte(expectedPlaintext)) { + t.Errorf("bad plaintext, got:%x want:%x", b.Plaintext, expectedPlaintext) } keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey)) @@ -41,6 +39,11 @@ func TestParse(t *testing.T) { } } +func TestParse(t *testing.T) { + testParse(t, clearsignInput, "Hello world\r\nline 2", "Hello world\nline 2\n") + testParse(t, clearsignInput2, "\r\n\r\n(This message has a couple of blank lines at the start and end.)\r\n\r\n", "\n\n(This message has a couple of blank lines at the start and end.)\n\n\n") +} + func TestParseWithNoNewlineAtEnd(t *testing.T) { input := clearsignInput input = input[:len(input)-len("trailing")-1] @@ -125,6 +128,29 @@ MyTpno24AjIAGb+mH1U= -----END PGP SIGNATURE----- trailing`) +var clearsignInput2 = []byte(` +asdlfkjasdlkfjsadf + +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + + + +(This message has a couple of blank lines at the start and end.) + + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.11 (GNU/Linux) + +iJwEAQEIAAYFAlPpSREACgkQO9o98PRieSpZTAP+M8QUoCt/7Rf3YbXPcdzIL32v +pt1I+cMNeopzfLy0u4ioEFi8s5VkwpL1AFmirvgViCwlf82inoRxzZRiW05JQ5LI +ESEzeCoy2LIdRCQ2hcrG8pIUPzUO4TqO5D/dMbdHwNH4h5nNmGJUAEG6FpURlPm+ +qZg6BaTvOxepqOxnhVU= +=e+C6 +-----END PGP SIGNATURE----- + +trailing`) + var signingKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.10 (GNU/Linux) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/elgamal/elgamal.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/elgamal/elgamal.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go index a553bdee..73f4fe37 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/elgamal/elgamal.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go @@ -10,7 +10,7 @@ // This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it // unsuitable for other protocols. RSA should be used in preference in any // case. -package elgamal +package elgamal // import "golang.org/x/crypto/openpgp/elgamal" import ( "crypto/rand" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/elgamal/elgamal_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/elgamal/elgamal_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/errors/errors.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/errors/errors.go similarity index 87% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/errors/errors.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/errors/errors.go index cb3f3196..eb0550b2 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/errors/errors.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/errors/errors.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package errors contains common error types for the OpenPGP packages. -package errors +package errors // import "golang.org/x/crypto/openpgp/errors" import ( "strconv" @@ -57,6 +57,14 @@ func (unknownIssuerError) Error() string { var ErrUnknownIssuer error = unknownIssuerError(0) +type keyRevokedError int + +func (keyRevokedError) Error() string { + return "openpgp: signature made by revoked key" +} + +var ErrKeyRevoked error = keyRevokedError(0) + type UnknownPacketTypeError uint8 func (upte UnknownPacketTypeError) Error() string { diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys.go similarity index 85% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys.go index f4f03518..c4a6640f 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/keys.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys.go @@ -5,10 +5,10 @@ package openpgp import ( - "code.google.com/p/go.crypto/openpgp/armor" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/packet" "crypto/rsa" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" "io" "time" ) @@ -23,10 +23,11 @@ var PrivateKeyType = "PGP PRIVATE KEY BLOCK" // (which must be a signing key), one or more identities claimed by that key, // and zero or more subkeys, which may be encryption keys. type Entity struct { - PrimaryKey *packet.PublicKey - PrivateKey *packet.PrivateKey - Identities map[string]*Identity // indexed by Identity.Name - Subkeys []Subkey + PrimaryKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Identities map[string]*Identity // indexed by Identity.Name + Revocations []*packet.Signature + Subkeys []Subkey } // An Identity represents an identity claimed by an Entity and zero or more @@ -59,6 +60,11 @@ type Key struct { type KeyRing interface { // KeysById returns the set of keys that have the given key id. KeysById(id uint64) []Key + // KeysByIdAndUsage returns the set of keys with the given id + // that also meet the key usage given by requiredUsage. + // The requiredUsage is expressed as the bitwise-OR of + // packet.KeyFlag* values. + KeysByIdUsage(id uint64, requiredUsage byte) []Key // DecryptionKeys returns all private keys that are valid for // decryption. DecryptionKeys() []Key @@ -173,6 +179,43 @@ func (el EntityList) KeysById(id uint64) (keys []Key) { return } +// KeysByIdAndUsage returns the set of keys with the given id that also meet +// the key usage given by requiredUsage. The requiredUsage is expressed as +// the bitwise-OR of packet.KeyFlag* values. +func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) { + for _, key := range el.KeysById(id) { + if len(key.Entity.Revocations) > 0 { + continue + } + + if key.SelfSignature.RevocationReason != nil { + continue + } + + if key.SelfSignature.FlagsValid && requiredUsage != 0 { + var usage byte + if key.SelfSignature.FlagCertify { + usage |= packet.KeyFlagCertify + } + if key.SelfSignature.FlagSign { + usage |= packet.KeyFlagSign + } + if key.SelfSignature.FlagEncryptCommunications { + usage |= packet.KeyFlagEncryptCommunications + } + if key.SelfSignature.FlagEncryptStorage { + usage |= packet.KeyFlagEncryptStorage + } + if usage&requiredUsage != requiredUsage { + continue + } + } + + keys = append(keys, key) + } + return +} + // DecryptionKeys returns all private keys that are valid for decryption. func (el EntityList) DecryptionKeys() (keys []Key) { for _, e := range el { @@ -290,6 +333,7 @@ func ReadEntity(packets *packet.Reader) (*Entity, error) { } var current *Identity + var revocations []*packet.Signature EachPacket: for { p, err := packets.Next() @@ -320,7 +364,7 @@ EachPacket: } if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId { - if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil { + if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil { return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error()) } current.SelfSignature = sig @@ -329,10 +373,17 @@ EachPacket: current.Signatures = append(current.Signatures, sig) } case *packet.Signature: - if current == nil { + if pkt.SigType == packet.SigTypeKeyRevocation { + revocations = append(revocations, pkt) + } else if pkt.SigType == packet.SigTypeDirectSignature { + // TODO: RFC4880 5.2.1 permits signatures + // directly on keys (eg. to bind additional + // revocation keys). + } else if current == nil { return nil, errors.StructuralError("signature packet found before user id packet") + } else { + current.Signatures = append(current.Signatures, pkt) } - current.Signatures = append(current.Signatures, pkt) case *packet.PrivateKey: if pkt.IsSubkey == false { packets.Unread(p) @@ -360,6 +411,16 @@ EachPacket: return nil, errors.StructuralError("entity without any identities") } + for _, revocation := range revocations { + err = e.PrimaryKey.VerifyRevocationSignature(revocation) + if err == nil { + e.Revocations = append(e.Revocations, revocation) + } else { + // TODO: RFC 4880 5.2.3.15 defines revocation keys. + return nil, errors.StructuralError("revocation signature signed by alternate key") + } + } + return e, nil } @@ -379,7 +440,7 @@ func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *p if !ok { return errors.StructuralError("subkey packet not followed by signature") } - if subKey.Sig.SigType != packet.SigTypeSubkeyBinding { + if subKey.Sig.SigType != packet.SigTypeSubkeyBinding && subKey.Sig.SigType != packet.SigTypeSubkeyRevocation { return errors.StructuralError("subkey signature with wrong type") } err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig) @@ -555,7 +616,7 @@ func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Co CreationTime: config.Now(), IssuerKeyId: &signer.PrivateKey.KeyId, } - if err := sig.SignKey(e.PrimaryKey, signer.PrivateKey, config); err != nil { + if err := sig.SignUserId(identity, e.PrimaryKey, signer.PrivateKey, config); err != nil { return err } ident.Signatures = append(ident.Signatures, sig) diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys_test.go new file mode 100644 index 00000000..e0625cb4 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/keys_test.go @@ -0,0 +1,216 @@ +package openpgp + +import ( + "testing" + "time" + + "golang.org/x/crypto/openpgp/packet" +) + +func TestKeyExpiry(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(expiringKeyHex)) + entity := kring[0] + + const timeFormat = "2006-01-02" + time1, _ := time.Parse(timeFormat, "2013-07-01") + // The expiringKeyHex key is structured as: + // + // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC + // sub 1024R/1ABB25A0 created: 2013-07-01 expires: 2013-07-08 usage: E + // sub 1024R/96A672F5 created: 2013-07-01 expires: 2013-07-31 usage: E + // + // So this should select the first, non-expired encryption key. + key, _ := entity.encryptionKey(time1) + if id := key.PublicKey.KeyIdShortString(); id != "1ABB25A0" { + t.Errorf("Expected key 1ABB25A0 at time %s, but got key %s", time1.Format(timeFormat), id) + } + + // Once the first encryption subkey has expired, the second should be + // selected. + time2, _ := time.Parse(timeFormat, "2013-07-09") + key, _ = entity.encryptionKey(time2) + if id := key.PublicKey.KeyIdShortString(); id != "96A672F5" { + t.Errorf("Expected key 96A672F5 at time %s, but got key %s", time2.Format(timeFormat), id) + } + + // Once all the keys have expired, nothing should be returned. + time3, _ := time.Parse(timeFormat, "2013-08-01") + if key, ok := entity.encryptionKey(time3); ok { + t.Errorf("Expected no key at time %s, but got key %s", time3.Format(timeFormat), key.PublicKey.KeyIdShortString()) + } +} + +// TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission. +func TestExternallyRevocableKey(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex)) + + // The 0xA42704B92866382A key can be revoked by 0xBE3893CB843D0FE70C + // according to this signature that appears within the key: + // :signature packet: algo 1, keyid A42704B92866382A + // version 4, created 1396409682, md5len 0, sigclass 0x1f + // digest algo 2, begin of digest a9 84 + // hashed subpkt 2 len 4 (sig created 2014-04-02) + // hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=CE094AA433F7040BB2DDF0BE3893CB843D0FE70C) + // hashed subpkt 7 len 1 (not revocable) + // subpkt 16 len 8 (issuer key ID A42704B92866382A) + // data: [1024 bits] + + id := uint64(0xA42704B92866382A) + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected to find key id %X, but got %d matches", id, len(keys)) + } +} + +func TestKeyRevocation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(revokedKeyHex)) + + // revokedKeyHex contains these keys: + // pub 1024R/9A34F7C0 2014-03-25 [revoked: 2014-03-25] + // sub 1024R/1BA3CD60 2014-03-25 [revoked: 2014-03-25] + ids := []uint64{0xA401D9F09A34F7C0, 0x5CD3BE0A1BA3CD60} + + for _, id := range ids { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find revoked key %X, but got %d matches", id, len(keys)) + } + keys = kring.KeysByIdUsage(id, 0) + if len(keys) != 0 { + t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", id, len(keys)) + } + } +} + +func TestSubkeyRevocation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(revokedSubkeyHex)) + + // revokedSubkeyHex contains these keys: + // pub 1024R/4EF7E4BECCDE97F0 2014-03-25 + // sub 1024R/D63636E2B96AE423 2014-03-25 + // sub 1024D/DBCE4EE19529437F 2014-03-25 + // sub 1024R/677815E371C2FD23 2014-03-25 [revoked: 2014-03-25] + validKeys := []uint64{0x4EF7E4BECCDE97F0, 0xD63636E2B96AE423, 0xDBCE4EE19529437F} + revokedKey := uint64(0x677815E371C2FD23) + + for _, id := range validKeys { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", id, len(keys)) + } + keys = kring.KeysByIdUsage(id, 0) + if len(keys) != 1 { + t.Errorf("Expected KeysByIdUsage to find key %X, but got %d matches", id, len(keys)) + } + } + + keys := kring.KeysById(revokedKey) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", revokedKey, len(keys)) + } + + keys = kring.KeysByIdUsage(revokedKey, 0) + if len(keys) != 0 { + t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", revokedKey, len(keys)) + } +} + +func TestKeyUsage(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex)) + + // subkeyUsageHex contains these keys: + // pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC + // sub 1024R/936C9153 created: 2014-04-01 expires: never usage: E + // sub 1024R/64D5F5BB created: 2014-04-02 expires: never usage: E + // sub 1024D/BC0BA992 created: 2014-04-02 expires: never usage: S + certifiers := []uint64{0xA42704B92866382A} + signers := []uint64{0xA42704B92866382A, 0x42CE2C64BC0BA992} + encrypters := []uint64{0x09C0C7D9936C9153, 0xC104E98664D5F5BB} + + for _, id := range certifiers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagCertify) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find certifier key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for certifier key id %X, but got %d matches", id, len(keys)) + } + } + + for _, id := range signers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find signing key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for signing key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) != 0 { + t.Errorf("Unexpected match for encryption key id %X", id) + } + } + + for _, id := range encrypters { + keys := kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find encryption key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for encryption key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) != 0 { + t.Errorf("Unexpected match for signing key id %X", id) + } + } +} + +func TestIdVerification(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + if err != nil { + t.Fatal(err) + } + if err := kring[1].PrivateKey.Decrypt([]byte("passphrase")); err != nil { + t.Fatal(err) + } + + const identity = "Test Key 1 (RSA)" + if err := kring[0].SignIdentity(identity, kring[1], nil); err != nil { + t.Fatal(err) + } + + ident, ok := kring[0].Identities[identity] + if !ok { + t.Fatal("identity missing from key after signing") + } + + checked := false + for _, sig := range ident.Signatures { + if sig.IssuerKeyId == nil || *sig.IssuerKeyId != kring[1].PrimaryKey.KeyId { + continue + } + + if err := kring[1].PrimaryKey.VerifyUserIdSignature(identity, kring[0].PrimaryKey, sig); err != nil { + t.Fatalf("error verifying new identity signature: %s", err) + } + checked = true + break + } + + if !checked { + t.Fatal("didn't find identity signature in Entity") + } +} + +const expiringKeyHex = "988d0451d1ec5d010400ba3385721f2dc3f4ab096b2ee867ab77213f0a27a8538441c35d2fa225b08798a1439a66a5150e6bdc3f40f5d28d588c712394c632b6299f77db8c0d48d37903fb72ebd794d61be6aa774688839e5fdecfe06b2684cc115d240c98c66cb1ef22ae84e3aa0c2b0c28665c1e7d4d044e7f270706193f5223c8d44e0d70b7b8da830011010001b40f4578706972792074657374206b657988be041301020028050251d1ec5d021b03050900278d00060b090807030206150802090a0b0416020301021e01021780000a091072589ad75e237d8c033503fd10506d72837834eb7f994117740723adc39227104b0d326a1161871c0b415d25b4aedef946ca77ea4c05af9c22b32cf98be86ab890111fced1ee3f75e87b7cc3c00dc63bbc85dfab91c0dc2ad9de2c4d13a34659333a85c6acc1a669c5e1d6cecb0cf1e56c10e72d855ae177ddc9e766f9b2dda57ccbb75f57156438bbdb4e42b88d0451d1ec5d0104009c64906559866c5cb61578f5846a94fcee142a489c9b41e67b12bb54cfe86eb9bc8566460f9a720cb00d6526fbccfd4f552071a8e3f7744b1882d01036d811ee5a3fb91a1c568055758f43ba5d2c6a9676b012f3a1a89e47bbf624f1ad571b208f3cc6224eb378f1645dd3d47584463f9eadeacfd1ce6f813064fbfdcc4b5a53001101000188a504180102000f021b0c050251d1f06b050900093e89000a091072589ad75e237d8c20e00400ab8310a41461425b37889c4da28129b5fae6084fafbc0a47dd1adc74a264c6e9c9cc125f40462ee1433072a58384daef88c961c390ed06426a81b464a53194c4e291ddd7e2e2ba3efced01537d713bd111f48437bde2363446200995e8e0d4e528dda377fd1e8f8ede9c8e2198b393bd86852ce7457a7e3daf74d510461a5b77b88d0451d1ece8010400b3a519f83ab0010307e83bca895170acce8964a044190a2b368892f7a244758d9fc193482648acb1fb9780d28cc22d171931f38bb40279389fc9bf2110876d4f3db4fcfb13f22f7083877fe56592b3b65251312c36f83ffcb6d313c6a17f197dd471f0712aad15a8537b435a92471ba2e5b0c72a6c72536c3b567c558d7b6051001101000188a504180102000f021b0c050251d1f07b050900279091000a091072589ad75e237d8ce69e03fe286026afacf7c97ee20673864d4459a2240b5655219950643c7dba0ac384b1d4359c67805b21d98211f7b09c2a0ccf6410c8c04d4ff4a51293725d8d6570d9d8bb0e10c07d22357caeb49626df99c180be02d77d1fe8ed25e7a54481237646083a9f89a11566cd20b9e995b1487c5f9e02aeb434f3a1897cd416dd0a87861838da3e9e" +const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98" +const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f" +const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/compressed.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/compressed.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/compressed.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/compressed.go index f1bd2f53..e8f0b5ca 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/compressed.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/compressed.go @@ -5,10 +5,10 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/errors" "compress/bzip2" "compress/flate" "compress/zlib" + "golang.org/x/crypto/openpgp/errors" "io" "strconv" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/compressed_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/compressed_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/compressed_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/compressed_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/config.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/config.go similarity index 71% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/config.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/config.go index 8c4f213c..d977cde6 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/config.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/config.go @@ -32,6 +32,17 @@ type Config struct { DefaultCompressionAlgo CompressionAlgo // CompressionConfig configures the compression settings. CompressionConfig *CompressionConfig + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int } func (c *Config) Random() io.Reader { @@ -68,3 +79,10 @@ func (c *Config) Compression() CompressionAlgo { } return c.DefaultCompressionAlgo } + +func (c *Config) PasswordHashIterations() int { + if c == nil || c.S2KCount == 0 { + return 0 + } + return c.S2KCount +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/encrypted_key.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/encrypted_key.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go index be96bf80..b43522cd 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/encrypted_key.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go @@ -5,10 +5,10 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/elgamal" - "code.google.com/p/go.crypto/openpgp/errors" "crypto/rsa" "encoding/binary" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" "io" "math/big" "strconv" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/encrypted_key_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/encrypted_key_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/literal.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/literal.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/literal.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/literal.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/ocfb.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/ocfb.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/ocfb.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/ocfb.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/ocfb_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/ocfb_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/one_pass_signature.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/one_pass_signature.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go index 8b5c547e..17135033 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/one_pass_signature.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go @@ -5,10 +5,10 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/s2k" "crypto" "encoding/binary" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" "io" "strconv" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/opaque.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/opaque.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/opaque.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/opaque.go index 5f9ad44c..f2e00b98 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/opaque.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/opaque.go @@ -6,7 +6,7 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/errors" "io" "io/ioutil" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/opaque_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/opaque_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/opaque_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/opaque_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet.go index 832b33f5..2e6b3498 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet.go @@ -4,15 +4,15 @@ // Package packet implements parsing and serialization of OpenPGP packets, as // specified in RFC 4880. -package packet +package packet // import "golang.org/x/crypto/openpgp/packet" import ( "bufio" - "code.google.com/p/go.crypto/cast5" - "code.google.com/p/go.crypto/openpgp/errors" "crypto/aes" "crypto/cipher" "crypto/des" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/openpgp/errors" "io" "math/big" ) @@ -385,13 +385,16 @@ func Read(r io.Reader) (p Packet, err error) { type SignatureType uint8 const ( - SigTypeBinary SignatureType = 0 - SigTypeText = 1 - SigTypeGenericCert = 0x10 - SigTypePersonaCert = 0x11 - SigTypeCasualCert = 0x12 - SigTypePositiveCert = 0x13 - SigTypeSubkeyBinding = 0x18 + SigTypeBinary SignatureType = 0 + SigTypeText = 1 + SigTypeGenericCert = 0x10 + SigTypePersonaCert = 0x11 + SigTypeCasualCert = 0x12 + SigTypePositiveCert = 0x13 + SigTypeSubkeyBinding = 0x18 + SigTypeDirectSignature = 0x1F + SigTypeKeyRevocation = 0x20 + SigTypeSubkeyRevocation = 0x28 ) // PublicKeyAlgorithm represents the different public key system specified for diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet_test.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet_test.go index c2fd72be..1dab5c3d 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/packet_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/packet_test.go @@ -6,9 +6,9 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/errors" "encoding/hex" "fmt" + "golang.org/x/crypto/openpgp/errors" "io" "io/ioutil" "testing" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key.go index 98cc14b1..9685a342 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key.go @@ -6,13 +6,13 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/elgamal" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/s2k" "crypto/cipher" "crypto/dsa" "crypto/rsa" "crypto/sha1" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" "io" "io/ioutil" "math/big" @@ -196,8 +196,8 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error { block := pk.cipher.new(key) cfb := cipher.NewCFBDecrypter(block, pk.iv) - data := pk.encryptedData - cfb.XORKeyStream(data, data) + data := make([]byte, len(pk.encryptedData)) + cfb.XORKeyStream(data, pk.encryptedData) if pk.sha1Checksum { if len(data) < sha1.Size { diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key_test.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key_test.go index 35d8951a..6a6197ae 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/private_key_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/private_key_test.go @@ -38,6 +38,12 @@ func TestPrivateKeyRead(t *testing.T) { continue } + err = privKey.Decrypt([]byte("wrong password")) + if err == nil { + t.Errorf("#%d: decrypted with incorrect key", i) + continue + } + err = privKey.Decrypt([]byte("testing")) if err != nil { t.Errorf("#%d: failed to decrypt: %s", i, err) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key.go similarity index 94% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key.go index 0fe312f3..26bd9ccd 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key.go @@ -6,8 +6,6 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/elgamal" - "code.google.com/p/go.crypto/openpgp/errors" "crypto" "crypto/dsa" "crypto/ecdsa" @@ -18,6 +16,8 @@ import ( _ "crypto/sha512" "encoding/binary" "fmt" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" "hash" "io" "math/big" @@ -572,6 +572,29 @@ func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err return pk.VerifySignature(h, sig) } +func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { + if !hashFunc.Available() { + return nil, errors.UnsupportedError("hash function") + } + h = hashFunc.New() + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + + return +} + +// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this +// public key. +func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { + h, err := keyRevocationHash(pk, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignature(h, sig) +} + // userIdSignatureHash returns a Hash of the message that needs to be signed // to assert that pk is a valid key for id. func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) { @@ -597,9 +620,9 @@ func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash } // VerifyUserIdSignature returns nil iff sig is a valid signature, made by this -// public key, of id. -func (pk *PublicKey) VerifyUserIdSignature(id string, sig *Signature) (err error) { - h, err := userIdSignatureHash(id, pk, sig.Hash) +// public key, that id is the identity of pub. +func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { + h, err := userIdSignatureHash(id, pub, sig.Hash) if err != nil { return err } @@ -607,9 +630,9 @@ func (pk *PublicKey) VerifyUserIdSignature(id string, sig *Signature) (err error } // VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this -// public key, of id. -func (pk *PublicKey) VerifyUserIdSignatureV3(id string, sig *SignatureV3) (err error) { - h, err := userIdSignatureV3Hash(id, pk, sig.Hash) +// public key, that id is the identity of pub. +func (pk *PublicKey) VerifyUserIdSignatureV3(id string, pub *PublicKey, sig *SignatureV3) (err error) { + h, err := userIdSignatureV3Hash(id, pub, sig.Hash) if err != nil { return err } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_test.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_test.go index 47168a41..7ad7d918 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_test.go @@ -133,7 +133,7 @@ func TestEcc384Serialize(t *testing.T) { t.Error(err) } uidSig := p.(*Signature) - err = pubkey.VerifyUserIdSignature(uid.Id, uidSig) + err = pubkey.VerifyUserIdSignature(uid.Id, pubkey, uidSig) if err != nil { t.Error(err, ": UID") } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_v3.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_v3.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go index c6adf75b..16412480 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_v3.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go @@ -16,7 +16,7 @@ import ( "strconv" "time" - "code.google.com/p/go.crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/errors" ) // PublicKeyV3 represents older, version 3 public keys. These keys are less secure and @@ -215,8 +215,8 @@ func (pk *PublicKeyV3) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (er } // VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this -// public key, of id. -func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, sig *SignatureV3) (err error) { +// public key, that id is the identity of pub. +func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, pub *PublicKeyV3, sig *SignatureV3) (err error) { h, err := userIdSignatureV3Hash(id, pk, sig.Hash) if err != nil { return err @@ -237,9 +237,10 @@ func (pk *PublicKeyV3) VerifyKeySignatureV3(signed *PublicKeyV3, sig *SignatureV // userIdSignatureV3Hash returns a Hash of the message that needs to be signed // to assert that pk is a valid key for id. func userIdSignatureV3Hash(id string, pk signingKey, hfn crypto.Hash) (h hash.Hash, err error) { - if h = hfn.New(); h == nil { + if !hfn.Available() { return nil, errors.UnsupportedError("hash function") } + h = hfn.New() // RFC 4880, section 5.2.4 pk.SerializeSignaturePrefix(h) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_v3_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/public_key_v3_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/reader.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/reader.go similarity index 96% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/reader.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/reader.go index 3325d636..753bf8f6 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/reader.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/reader.go @@ -5,7 +5,7 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/errors" "io" ) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature.go similarity index 94% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature.go index dbed36ed..79cf993f 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature.go @@ -5,18 +5,26 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/s2k" "crypto" "crypto/dsa" "crypto/rsa" "encoding/binary" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" "hash" "io" "strconv" "time" ) +const ( + // See RFC 4880, section 5.2.3.21 for details. + KeyFlagCertify = 1 << iota + KeyFlagSign + KeyFlagEncryptCommunications + KeyFlagEncryptStorage +) + // Signature represents a signature. See RFC 4880, section 5.2. type Signature struct { SigType SignatureType @@ -50,6 +58,11 @@ type Signature struct { FlagsValid bool FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool + // RevocationReason is set if this signature has been revoked. + // See RFC 4880, section 5.2.3.23 for details. + RevocationReason *uint8 + RevocationReasonText string + outSubpackets []outputSubpacket } @@ -176,6 +189,7 @@ const ( prefCompressionSubpacket signatureSubpacketType = 22 primaryUserIdSubpacket signatureSubpacketType = 25 keyFlagsSubpacket signatureSubpacketType = 27 + reasonForRevocationSubpacket signatureSubpacketType = 29 ) // parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. @@ -305,18 +319,30 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r return } sig.FlagsValid = true - if subpacket[0]&1 != 0 { + if subpacket[0]&KeyFlagCertify != 0 { sig.FlagCertify = true } - if subpacket[0]&2 != 0 { + if subpacket[0]&KeyFlagSign != 0 { sig.FlagSign = true } - if subpacket[0]&4 != 0 { + if subpacket[0]&KeyFlagEncryptCommunications != 0 { sig.FlagEncryptCommunications = true } - if subpacket[0]&8 != 0 { + if subpacket[0]&KeyFlagEncryptStorage != 0 { sig.FlagEncryptStorage = true } + case reasonForRevocationSubpacket: + // Reason For Revocation, section 5.2.3.23 + if !isHashed { + return + } + if len(subpacket) == 0 { + err = errors.StructuralError("empty revocation reason subpacket") + return + } + sig.RevocationReason = new(uint8) + *sig.RevocationReason = subpacket[0] + sig.RevocationReasonText = string(subpacket[1:]) default: if isCritical { @@ -489,7 +515,7 @@ func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, co return sig.Sign(h, priv, config) } -// SignKey computes a signature from priv, asserting that pub is a subkey. On +// SignKey computes a signature from priv, asserting that pub is a subkey. On // success, the signature is stored in sig. Call Serialize to write it out. // If config is nil, sensible defaults will be used. func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error { @@ -595,16 +621,16 @@ func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) { if sig.FlagsValid { var flags byte if sig.FlagCertify { - flags |= 1 + flags |= KeyFlagCertify } if sig.FlagSign { - flags |= 2 + flags |= KeyFlagSign } if sig.FlagEncryptCommunications { - flags |= 4 + flags |= KeyFlagEncryptCommunications } if sig.FlagEncryptStorage { - flags |= 8 + flags |= KeyFlagEncryptStorage } subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}}) } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3.go index 1bba74df..6edff889 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3.go @@ -12,8 +12,8 @@ import ( "strconv" "time" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/s2k" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" ) // SignatureV3 represents older version 3 signatures. These signatures are less secure diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go index 57f26b59..ad7b62ac 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/signature_v3_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go @@ -12,7 +12,7 @@ import ( "io/ioutil" "testing" - "code.google.com/p/go.crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/armor" ) func TestSignatureV3Read(t *testing.T) { diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetric_key_encrypted.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go similarity index 96% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetric_key_encrypted.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go index a78344ff..7dff6552 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetric_key_encrypted.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go @@ -6,11 +6,12 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/s2k" "crypto/cipher" "io" "strconv" + + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" ) // This is the largest session key that we'll support. Since no 512-bit cipher @@ -119,7 +120,7 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf keyEncryptingKey := make([]byte, keySize) // s2k.Serialize salts and stretches the passphrase, and writes the // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. - err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase) + err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.PasswordHashIterations()}) if err != nil { return } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetric_key_encrypted_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetric_key_encrypted_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go index 04dd670f..6126030e 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go @@ -5,10 +5,10 @@ package packet import ( - "code.google.com/p/go.crypto/openpgp/errors" "crypto/cipher" "crypto/sha1" "crypto/subtle" + "golang.org/x/crypto/openpgp/errors" "hash" "io" "strconv" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go index c4845eb2..c5c00f7b 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/symmetrically_encrypted_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go @@ -6,9 +6,9 @@ package packet import ( "bytes" - "code.google.com/p/go.crypto/openpgp/errors" "crypto/sha1" "encoding/hex" + "golang.org/x/crypto/openpgp/errors" "io" "io/ioutil" "testing" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userattribute.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userattribute.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userattribute.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userattribute.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userattribute_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userattribute_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userid.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userid.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userid.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userid.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userid_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userid_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/packet/userid_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/packet/userid_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read.go index 4bdb8345..c50a509a 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read.go @@ -3,14 +3,14 @@ // license that can be found in the LICENSE file. // Package openpgp implements high level operations on OpenPGP messages. -package openpgp +package openpgp // import "golang.org/x/crypto/openpgp" import ( - "code.google.com/p/go.crypto/openpgp/armor" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/packet" "crypto" _ "crypto/sha256" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" "hash" "io" "strconv" @@ -249,13 +249,9 @@ FindLiteralData: md.IsSigned = true md.SignedByKeyId = p.KeyId - keys := keyring.KeysById(p.KeyId) - for i, key := range keys { - if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign { - continue - } - md.SignedBy = &keys[i] - break + keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign) + if len(keys) > 0 { + md.SignedBy = &keys[0] } case *packet.LiteralData: md.LiteralData = p @@ -280,10 +276,10 @@ FindLiteralData: // returns two hashes. The second should be used to hash the message itself and // performs any needed preprocessing. func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) { - h := hashId.New() - if h == nil { + if !hashId.Available() { return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId))) } + h := hashId.New() switch sigType { case packet.SigTypeBinary: @@ -382,11 +378,6 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signe return nil, errors.StructuralError("non signature packet found") } - keys := keyring.KeysById(issuerKeyId) - if len(keys) == 0 { - return nil, errors.ErrUnknownIssuer - } - h, wrappedHash, err := hashForSignature(hashFunc, sigType) if err != nil { return @@ -397,10 +388,12 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signe return } + keys := keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) + if len(keys) == 0 { + return nil, errors.ErrUnknownIssuer + } + for _, key := range keys { - if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign { - continue - } switch sig := p.(type) { case *packet.Signature: err = key.PublicKey.VerifySignature(h, sig) @@ -412,11 +405,10 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signe } } - if err != nil { - return + if err == nil { + err = errors.ErrUnknownIssuer } - - return nil, errors.ErrUnknownIssuer + return nil, err } // CheckArmoredDetachedSignature performs the same actions as diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read_test.go similarity index 89% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read_test.go index 3c908373..c2720ae5 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/read_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/read_test.go @@ -6,11 +6,12 @@ package openpgp import ( "bytes" - "code.google.com/p/go.crypto/openpgp/errors" _ "crypto/sha512" "encoding/hex" + "golang.org/x/crypto/openpgp/errors" "io" "io/ioutil" + "strings" "testing" ) @@ -278,6 +279,15 @@ func TestDetachedSignature(t *testing.T) { testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId) testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId) testDetachedSignature(t, kring, readerFromHex(detachedSignatureV3TextHex), signedInput, "v3", testKey1KeyId) + + incorrectSignedInput := signedInput + "X" + _, err := CheckDetachedSignature(kring, bytes.NewBufferString(incorrectSignedInput), readerFromHex(detachedSignatureHex)) + if err == nil { + t.Fatal("CheckDetachedSignature returned without error for bad signature") + } + if err == errors.ErrUnknownIssuer { + t.Fatal("CheckDetachedSignature returned ErrUnknownIssuer when the signer was known, but the signature invalid") + } } func TestDetachedSignatureDSA(t *testing.T) { @@ -285,6 +295,33 @@ func TestDetachedSignatureDSA(t *testing.T) { testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId) } +func testHashFunctionError(t *testing.T, signatureHex string) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + _, err := CheckDetachedSignature(kring, nil, readerFromHex(signatureHex)) + if err == nil { + t.Fatal("Packet with bad hash type was correctly parsed") + } + unsupported, ok := err.(errors.UnsupportedError) + if !ok { + t.Fatalf("Unexpected class of error: %s", err) + } + if !strings.Contains(string(unsupported), "hash ") { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestUnknownHashFunction(t *testing.T) { + // unknownHashFunctionHex contains a signature packet with hash + // function type 153 (which isn't a real hash function id). + testHashFunctionError(t, unknownHashFunctionHex) +} + +func TestMissingHashFunction(t *testing.T) { + // missingHashFunctionHex contains a signature packet that uses + // RIPEMD160, which isn't compiled in. + testHashFunctionError(t, missingHashFunctionHex) +} + func TestReadingArmoredPrivateKey(t *testing.T) { el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock)) if err != nil { @@ -373,3 +410,7 @@ VrM0m72/jnpKo04= -----END PGP PRIVATE KEY BLOCK-----` const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003` + +const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` + +const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k.go similarity index 61% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k.go index 33462cc8..0e8641ed 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k.go @@ -4,16 +4,90 @@ // Package s2k implements the various OpenPGP string-to-key transforms as // specified in RFC 4800 section 3.7.1. -package s2k +package s2k // import "golang.org/x/crypto/openpgp/s2k" import ( - "code.google.com/p/go.crypto/openpgp/errors" "crypto" "hash" "io" "strconv" + + "golang.org/x/crypto/openpgp/errors" ) +// Config collects configuration parameters for s2k key-stretching +// transformatioms. A nil *Config is valid and results in all default +// values. Currently, Config is used only by the Serialize function in +// this package. +type Config struct { + // Hash is the default hash function to be used. If + // nil, SHA1 is used. + Hash crypto.Hash + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int +} + +func (c *Config) hash() crypto.Hash { + if c == nil || uint(c.Hash) == 0 { + // SHA1 is the historical default in this package. + return crypto.SHA1 + } + + return c.Hash +} + +func (c *Config) encodedCount() uint8 { + if c == nil || c.S2KCount == 0 { + return 96 // The common case. Correspoding to 65536 + } + + i := c.S2KCount + switch { + // Behave like GPG. Should we make 65536 the lowest value used? + case i < 1024: + i = 1024 + case i > 65011712: + i = 65011712 + } + + return encodeCount(i) +} + +// encodeCount converts an iterative "count" in the range 1024 to +// 65011712, inclusive, to an encoded count. The return value is the +// octet that is actually stored in the GPG file. encodeCount panics +// if i is not in the above range (encodedCount above takes care to +// pass i in the correct range). See RFC 4880 Section 3.7.7.1. +func encodeCount(i int) uint8 { + if i < 1024 || i > 65011712 { + panic("count arg i outside the required range") + } + + for encoded := 0; encoded < 256; encoded++ { + count := decodeCount(uint8(encoded)) + if count >= i { + return uint8(encoded) + } + } + + return 255 +} + +// decodeCount returns the s2k mode 3 iterative "count" corresponding to +// the encoded octet c. +func decodeCount(c uint8) int { + return (16 + int(c&15)) << (uint32(c>>4) + 6) +} + // Simple writes to out the result of computing the Simple S2K function (RFC // 4880, section 3.7.1.1) using the given hash and input passphrase. func Simple(out []byte, h hash.Hash, in []byte) { @@ -91,10 +165,10 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) { if !ok { return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1]))) } - h := hash.New() - if h == nil { + if !hash.Available() { return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash))) } + h := hash.New() switch buf[0] { case 0: @@ -116,7 +190,7 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) { if err != nil { return } - count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6) + count := decodeCount(buf[8]) f := func(out, in []byte) { Iterated(out, h, in, buf[:8], count) } @@ -126,23 +200,26 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) { return nil, errors.UnsupportedError("S2K function") } -// Serialize salts and stretches the given passphrase and writes the resulting -// key into key. It also serializes an S2K descriptor to w. -func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte) error { +// Serialize salts and stretches the given passphrase and writes the +// resulting key into key. It also serializes an S2K descriptor to +// w. The key stretching can be configured with c, which may be +// nil. In that case, sensible defaults will be used. +func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error { var buf [11]byte buf[0] = 3 /* iterated and salted */ - buf[1], _ = HashToHashId(crypto.SHA1) + buf[1], _ = HashToHashId(c.hash()) salt := buf[2:10] if _, err := io.ReadFull(rand, salt); err != nil { return err } - const count = 65536 // this is the default in gpg - buf[10] = 96 // 65536 iterations + encodedCount := c.encodedCount() + count := decodeCount(encodedCount) + buf[10] = encodedCount if _, err := w.Write(buf[:]); err != nil { return err } - Iterated(key, crypto.SHA1.New(), passphrase, salt, count) + Iterated(key, c.hash().New(), passphrase, salt, count) return nil } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go similarity index 81% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go index 48988280..183d2605 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/s2k/s2k_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go @@ -6,10 +6,16 @@ package s2k import ( "bytes" + "crypto" + _ "crypto/md5" "crypto/rand" "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" "encoding/hex" "testing" + + _ "golang.org/x/crypto/ripemd160" ) var saltedTests = []struct { @@ -96,10 +102,23 @@ func TestParse(t *testing.T) { } func TestSerialize(t *testing.T) { + hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160, + crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224} + testCounts := []int{-1, 0, 1024, 65536, 4063232, 65011712} + for _, h := range hashes { + for _, c := range testCounts { + testSerializeConfig(t, &Config{Hash: h, S2KCount: c}) + } + } +} + +func testSerializeConfig(t *testing.T, c *Config) { + t.Logf("Running testSerializeConfig() with config: %+v", c) + buf := bytes.NewBuffer(nil) key := make([]byte, 16) passphrase := []byte("testing") - err := Serialize(buf, key, rand.Reader, passphrase) + err := Serialize(buf, key, rand.Reader, passphrase, c) if err != nil { t.Errorf("failed to serialize: %s", err) return diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/write.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/write.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/write.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/write.go index a67ffca5..e4711746 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/write.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/write.go @@ -5,11 +5,11 @@ package openpgp import ( - "code.google.com/p/go.crypto/openpgp/armor" - "code.google.com/p/go.crypto/openpgp/errors" - "code.google.com/p/go.crypto/openpgp/packet" - "code.google.com/p/go.crypto/openpgp/s2k" "crypto" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" + "golang.org/x/crypto/openpgp/s2k" "hash" "io" "strconv" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/write_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/write_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/openpgp/write_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/openpgp/write_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/libotr_test_helper.c b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/libotr_test_helper.c similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/libotr_test_helper.c rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/libotr_test_helper.c diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/otr.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/otr.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/otr.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/otr.go index d98b3234..07ac080e 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/otr.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/otr.go @@ -4,7 +4,7 @@ // Package otr implements the Off The Record protocol as specified in // http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html -package otr +package otr // import "golang.org/x/crypto/otr" import ( "bytes" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/otr_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/otr_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/otr_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/otr_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/smp.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/smp.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/otr/smp.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/otr/smp.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/pbkdf2/pbkdf2.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/pbkdf2/pbkdf2.go index c02b4d5a..593f6530 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/pbkdf2/pbkdf2.go @@ -16,7 +16,7 @@ Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To choose, you can pass the `New` functions from the different SHA packages to pbkdf2.Key. */ -package pbkdf2 +package pbkdf2 // import "golang.org/x/crypto/pbkdf2" import ( "crypto/hmac" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/pbkdf2/pbkdf2_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/pbkdf2/pbkdf2_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/const_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/const_amd64.s similarity index 68% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/const_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/const_amd64.s index e26ea670..33fcd6ee 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/const_amd64.s +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/const_amd64.s @@ -8,38 +8,38 @@ // +build amd64,!gccgo DATA ·SCALE(SB)/8, $0x37F4000000000000 -GLOBL ·SCALE(SB), $8 +GLOBL ·SCALE(SB), 8, $8 DATA ·TWO32(SB)/8, $0x41F0000000000000 -GLOBL ·TWO32(SB), $8 +GLOBL ·TWO32(SB), 8, $8 DATA ·TWO64(SB)/8, $0x43F0000000000000 -GLOBL ·TWO64(SB), $8 +GLOBL ·TWO64(SB), 8, $8 DATA ·TWO96(SB)/8, $0x45F0000000000000 -GLOBL ·TWO96(SB), $8 +GLOBL ·TWO96(SB), 8, $8 DATA ·ALPHA32(SB)/8, $0x45E8000000000000 -GLOBL ·ALPHA32(SB), $8 +GLOBL ·ALPHA32(SB), 8, $8 DATA ·ALPHA64(SB)/8, $0x47E8000000000000 -GLOBL ·ALPHA64(SB), $8 +GLOBL ·ALPHA64(SB), 8, $8 DATA ·ALPHA96(SB)/8, $0x49E8000000000000 -GLOBL ·ALPHA96(SB), $8 +GLOBL ·ALPHA96(SB), 8, $8 DATA ·ALPHA130(SB)/8, $0x4C08000000000000 -GLOBL ·ALPHA130(SB), $8 +GLOBL ·ALPHA130(SB), 8, $8 DATA ·DOFFSET0(SB)/8, $0x4330000000000000 -GLOBL ·DOFFSET0(SB), $8 +GLOBL ·DOFFSET0(SB), 8, $8 DATA ·DOFFSET1(SB)/8, $0x4530000000000000 -GLOBL ·DOFFSET1(SB), $8 +GLOBL ·DOFFSET1(SB), 8, $8 DATA ·DOFFSET2(SB)/8, $0x4730000000000000 -GLOBL ·DOFFSET2(SB), $8 +GLOBL ·DOFFSET2(SB), 8, $8 DATA ·DOFFSET3(SB)/8, $0x4930000000000000 -GLOBL ·DOFFSET3(SB), $8 +GLOBL ·DOFFSET3(SB), 8, $8 DATA ·DOFFSET3MINUSTWO128(SB)/8, $0x492FFFFE00000000 -GLOBL ·DOFFSET3MINUSTWO128(SB), $8 +GLOBL ·DOFFSET3MINUSTWO128(SB), 8, $8 DATA ·HOFFSET0(SB)/8, $0x43300001FFFFFFFB -GLOBL ·HOFFSET0(SB), $8 +GLOBL ·HOFFSET0(SB), 8, $8 DATA ·HOFFSET1(SB)/8, $0x45300001FFFFFFFE -GLOBL ·HOFFSET1(SB), $8 +GLOBL ·HOFFSET1(SB), 8, $8 DATA ·HOFFSET2(SB)/8, $0x47300001FFFFFFFE -GLOBL ·HOFFSET2(SB), $8 +GLOBL ·HOFFSET2(SB), 8, $8 DATA ·HOFFSET3(SB)/8, $0x49300003FFFFFFFE -GLOBL ·HOFFSET3(SB), $8 +GLOBL ·HOFFSET3(SB), 8, $8 DATA ·ROUNDING(SB)/2, $0x137f -GLOBL ·ROUNDING(SB), $2 +GLOBL ·ROUNDING(SB), 8, $2 diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305.go index 2270d2b3..4a5f826f 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305.go @@ -16,7 +16,7 @@ used with a fixed key in order to generate one-time keys from an nonce. However, in this package AES isn't used and the one-time key is specified directly. */ -package poly1305 +package poly1305 // import "golang.org/x/crypto/poly1305" import "crypto/subtle" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/poly1305_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/poly1305_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/sum_amd64.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/sum_amd64.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/sum_amd64.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/sum_amd64.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/sum_ref.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/sum_ref.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/poly1305/sum_ref.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/poly1305/sum_ref.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160.go index da690f0b..6c6e8423 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package ripemd160 implements the RIPEMD-160 hash algorithm. -package ripemd160 +package ripemd160 // import "golang.org/x/crypto/ripemd160" // RIPEMD-160 is designed by by Hans Dobbertin, Antoon Bosselaers, and Bart // Preneel with specifications available at: diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160block.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160block.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ripemd160/ripemd160block.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ripemd160/ripemd160block.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/hsalsa20.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/hsalsa20.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/hsalsa20.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/hsalsa20.go index 4ba47d59..4c96147c 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/hsalsa20.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/hsalsa20.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package salsa provides low-level access to functions in the Salsa family. -package salsa +package salsa // import "golang.org/x/crypto/salsa20/salsa" // Sigma is the Salsa20 constant for 256-bit keys. var Sigma = [16]byte{'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k'} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa2020_amd64.s b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa2020_amd64.s similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa2020_amd64.s rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa2020_amd64.s diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa208.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa208.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa208.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa208.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa20_amd64.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa20_amd64.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa20_amd64.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa20_amd64.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa20_ref.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa20_ref.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa20_ref.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa20_ref.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa/salsa_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa/salsa_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa20.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa20.go similarity index 95% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa20.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa20.go index c308ca38..fde9846b 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa20.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa20.go @@ -19,12 +19,12 @@ This package also implements XSalsa20: a version of Salsa20 with a 24-byte nonce as specified in http://cr.yp.to/snuffle/xsalsa-20081128.pdf. Simply passing a 24-byte slice as the nonce triggers XSalsa20. */ -package salsa20 +package salsa20 // import "golang.org/x/crypto/salsa20" // TODO(agl): implement XORKeyStream12 and XORKeyStream8 - the reduced round variants of Salsa20. import ( - "code.google.com/p/go.crypto/salsa20/salsa" + "golang.org/x/crypto/salsa20/salsa" ) // XORKeyStream crypts bytes from in to out using the given key and nonce. In diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa20_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa20_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/salsa20/salsa20_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/salsa20/salsa20_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/scrypt/scrypt.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/scrypt/scrypt.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/scrypt/scrypt.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/scrypt/scrypt.go index f7a9fe1a..dc0124b1 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/scrypt/scrypt.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/scrypt/scrypt.go @@ -5,13 +5,13 @@ // Package scrypt implements the scrypt key derivation function as defined in // Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard // Functions" (http://www.tarsnap.com/scrypt/scrypt.pdf). -package scrypt +package scrypt // import "golang.org/x/crypto/scrypt" import ( "crypto/sha256" "errors" - "code.google.com/p/go.crypto/pbkdf2" + "golang.org/x/crypto/pbkdf2" ) const maxInt = int(^uint(0) >> 1) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/scrypt/scrypt_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/scrypt/scrypt_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/doc.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/doc.go new file mode 100644 index 00000000..027c8ad1 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/doc.go @@ -0,0 +1,68 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA-3 fixed-output-length hash functions and +// the SHAKE variable-output-length hash functions defined by FIPS-202. +// +// Both types of hash function use the "sponge" construction and the Keccak +// permutation. For a detailed specification see http://keccak.noekeon.org/ +// +// +// Guidance +// +// If you aren't sure what function you need, use SHAKE256 with at least 64 +// bytes of output. +// +// If you need a secret-key MAC (message authentication code), prepend the +// secret key to the input, hash with SHAKE256 and read at least 32 bytes of +// output. +// +// +// Security strengths +// +// The SHA3-x functions have a security strength against preimage attacks of x +// bits. Since they only produce x bits of output, their collision-resistance +// is only x/2 bits. +// +// The SHAKE-x functions have a generic security strength of x bits against +// all attacks, provided that at least 2x bits of their output is used. +// Requesting more than 2x bits of output does not increase the collision- +// resistance of the SHAKE functions. +// +// +// The sponge construction +// +// A sponge builds a pseudo-random function from a pseudo-random permutation, +// by applying the permutation to a state of "rate + capacity" bytes, but +// hiding "capacity" of the bytes. +// +// A sponge starts out with a zero state. To hash an input using a sponge, up +// to "rate" bytes of the input are XORed into the sponge's state. The sponge +// has thus been "filled up" and the permutation is applied. This process is +// repeated until all the input has been "absorbed". The input is then padded. +// The digest is "squeezed" from the sponge by the same method, except that +// output is copied out. +// +// A sponge is parameterized by its generic security strength, which is equal +// to half its capacity; capacity + rate is equal to the permutation's width. +// +// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means +// that security_strength == (1600 - bitrate) / 2. +// +// +// Recommendations, detailed +// +// The SHAKE functions are recommended for most new uses. They can produce +// output of arbitrary length. SHAKE256, with an output length of at least +// 64 bytes, provides 256-bit security against all attacks. +// +// The Keccak team recommends SHAKE256 for most applications upgrading from +// SHA2-512. (NIST chose a much stronger, but much slower, sponge instance +// for SHA3-512.) +// +// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions. +// They produce output of the same length, with the same security strengths +// against all attacks. This means, in particular, that SHA3-256 only has +// 128-bit collision resistance, because its output length is 32 bytes. +package sha3 // import "golang.org/x/crypto/sha3" diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/hashes.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/hashes.go new file mode 100644 index 00000000..2b51cf4e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/hashes.go @@ -0,0 +1,65 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This file provides functions for creating instances of the SHA-3 +// and SHAKE hash functions, as well as utility functions for hashing +// bytes. + +import ( + "hash" +) + +// New224 creates a new SHA3-224 hash. +// Its generic security strength is 224 bits against preimage attacks, +// and 112 bits against collision attacks. +func New224() hash.Hash { return &state{rate: 144, outputLen: 28, dsbyte: 0x06} } + +// New256 creates a new SHA3-256 hash. +// Its generic security strength is 256 bits against preimage attacks, +// and 128 bits against collision attacks. +func New256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x06} } + +// New384 creates a new SHA3-384 hash. +// Its generic security strength is 384 bits against preimage attacks, +// and 192 bits against collision attacks. +func New384() hash.Hash { return &state{rate: 104, outputLen: 48, dsbyte: 0x06} } + +// New512 creates a new SHA3-512 hash. +// Its generic security strength is 512 bits against preimage attacks, +// and 256 bits against collision attacks. +func New512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x06} } + +// Sum224 returns the SHA3-224 digest of the data. +func Sum224(data []byte) (digest [28]byte) { + h := New224() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum256 returns the SHA3-256 digest of the data. +func Sum256(data []byte) (digest [32]byte) { + h := New256() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum384 returns the SHA3-384 digest of the data. +func Sum384(data []byte) (digest [48]byte) { + h := New384() + h.Write(data) + h.Sum(digest[:0]) + return +} + +// Sum512 returns the SHA3-512 digest of the data. +func Sum512(data []byte) (digest [64]byte) { + h := New512() + h.Write(data) + h.Sum(digest[:0]) + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakKats.json.deflate b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakKats.json.deflate new file mode 100644 index 0000000000000000000000000000000000000000..62e85ae24236b46c09e5cfa84c71c69f5cc33cf6 GIT binary patch literal 521342 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%0DdSPIES;LF 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

;TQ8)#7l>4Snz*J)FMZ0Q-2>;ACWq)?04 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 z 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

nJLYnt<$PQi86UnxvK+ceSbB#mC5$ zWG^s$@|*!)MrkW5Vy?t9>lc(1O2F=7OwM9Ez3;B^G$%LKjia8*hkv>G{9|`&2@y2 zcg(5qltuZ$yTx}f>@koo@s`xpY=gn57?+iOC)5IaA4B0BorE7CBsI-~Ok21hB#)Q{ zQ>?iBA~)KAx5Hrna0^NfV7j#D0AlXMnYu-F_|#O<~kRV*yO8#m#AH@>gzW}hpDnu0p;W-8UPc;CK7jVFx<`-@>xbl8uMpSQZSFToUq>6g*A-byKQeb01+k3$`JuBIvtV zcoNh;K|CtVc6^X2B1A33OH(i!PH67&l*ysM5m@k@Fq-<|){;Hq=Rk{F*Xc$;FDY^> z=X@v9fWvvArfOY^CsNOivg&lvS5>KDc%QykVS$FkB?^*xgz(OU!CtxNECrty-|$(e zc2DHIMV5}!U4yg9=fISM?K%&knlLy391#r&O`pJP6a zc{%Y&@*&XF>a+K(UK>+2M@8vkfW9R%RZ5MJ#;M}ce*Q2KPn91af=KJX*qW(t(QQRO zjyrrLx2RKdor@V^IZsHIAt}ej=DR3WBl&6;1Y5={+|PY*{{^ z>^___NXXfT5oaFl2MyBgVvK%J-YkVgRpIP*;stBL_=0`zSx4I_P+czGB)^$T1ka15 zf!DXCQ1xP*{6^DWNAr7lmkF<5%DXhjL{>M%%4Y>HPHs^qD-woNR)?|$PY(GBW86hK zPCQUVF*%)#+!$k|ORqel>+PhH+Q#&8e<#lo5BFs^y36d!6Ud^orIPbTY{z~pP>XQ^ zNACt7QC4VW5=se#l@wxPMyYbk_B@Z=i6gpR^;`_&G+I%Zr_u%DZN7W3Z>T9zi~%x$ zT5ZP%C91U9)U3;s=hD?ljZ!@5&1{R-K7}(5ckF8B;Sz5opFjjZO4UbF(~3rAO5MPt zr-2)GQYDc3aIuI^LDH&hd^}!0b{EjMIJAX23&ZMKjnXrh*ULFNmTow&&#Ck<@3|Pi zjHnkEsN^m2>=7JGaw1hiO0`Qo?OjHDBm0PgHYn666nIQ1DKM;pWgoV&kmfuq$UcoFD#&3)}AZ zTv7rlUzX;u5o8ucr9XJDhr67OEvMsnI^-00P6><9aPOB3Oh^h>ec{-!%DvV*qBZxN z8utibC@RZw#>7C~!el4w_108vKfQTD3I_Kksa(0Jz(d_H;k?ndO~MPMRi}5EwTm-w zEPQ1+ILp^J8=Uk!XKQycY)+6s8xZZuDhpX>it_m^c9p*P5&r~7+WzCV?~TLkxbFj) z9YrrX00{xjO$)WaASu}cBhVRgiG2wWVDg%iSm5=@)@Z3yGnJ`IE7%HJ#UKZ8a*hSD zIN7o-x4Kc~UepBdu_%(-^JUr^ty~DhU?rx}T9xcac=PlY(Ub*K@Wvg6IA)Q+R37H8 z^~HlOOE{pM*>uC(y9-=vT5;NpYEr!d=w7__UY?W}TjbCDSgmQ#`2-;0pw~4F?AVQx z^~D;*toUK?aJyw}GLRU}M$#)dQhebT1KCBi)(-*kU`DhM*Oys^lj&)Zl!-|=q?X-f z(MCI{_9R-ZwZWC4r5~@?(@>?PIV#X{vD#ep1f0&o4N}p9;%=HIMHYSmgfnlNLTcjW zd)oLUL5|Lz2D;#a1B18vTLK>}iMK>S1kElMA+jfUhH28y3zotG)yL?oyppy-C>O$m zyP>ZhuB*dBqH|k;21y^2L%c^ll@DN(Ekry^?Kpy6N6N<)yM1gOfDeJSZ68MmF7V;? zgc)T=EI%sIYj8s#(^ZC3zI$Y+5zXubSz^9cO|6OqLI(8kR;(c@o+f&A-}p=AqI|nc z5Sr*vkXrr5Wc1znUK%+|EI_*yJ)F5k-U0~rR|u-d1^Y_XdMzJ&#k4yV(Y`P<6i;iJ zctxG+=q7g}08ip6R2Fbv5R;?U5GJOHfjBE@vm7~%h8tI$1SZD-`RFIN&%U^Q#!+H; zcl4l0RI_m~ulG5HNN(x|_}h}?*b1xC=E^)+H*Z(6VF9@i(0#G*)n)5ldDLO(Dw8L+n6s~Hx)L}#1TH+=09#`1q=8h#LhC(9Y|NHGtN!OmcD#=5* zu)`7$h)7?(INhV_?j|lv!>D$=n7T8r$To-?orKGmmkn#LDlAoglP{?dUPuW_t-Irh z?nu0u(&tZDPJtyEI-k8BPsXyKXTsVMZF1adZ!3@6;;Oe*po^~TVJAE5_7Hi$ekLIZ z2yZxIXUwqLL*59FR*PU3?Q!H~+jFn($yPBMweT}AG(<^fu?Nq<1{JWKMdSfv-)0b| zYqmRK$WyrTl_Ao{N$)5C>uH<&8*oi4c}umUdC#?@3?|vD;|!m|p%pDyXJYqzokJDc zI_f;ISUqnM3dS#e@-4(uc!TJVITX#7HL$W=aZh^Q;(_+K0Om^ACoCo987yn(&=~cyoa&C&FtuGE;c0XJg;Zg+7d9g%QzHL zN~*79yBcrvxKu=?s~-iWDLHi%%xgA*0-hTs*J9O(F-^C2p8`#e}KziUbEo^3UXx1)FtgjyTkWW~ad z4Z}{`JWY$ioweP9$pH-AR9M-W9_4!+Z^o{fB#L_RWwGGPj+r#tebU08;QIH0tW1%w z4U3_#Qhj9JI$X%0s0Re};W!k|(VBz%v>`}nRKhR7>bjxNvmm#jkFCXpbxUa{vZSi_ zrDmH80&>@a9(IiD;d+g z1s+m0b5cE(hG`^|-SAk_8Q6R;-BqifY$BK)0d_<*)EW;uzB%S8BRLh=Ix%i3akW6U z^}t4ON`7lOV?os5w~91DY8>7Clr5R$*cF1Q6%hN9IqEk?^}*HiPHwM8l>bn$E+HRj8Mjg7~k8WN$( z549lQ&X%pVw#q=8s4zQS0Xap3kUGf)kp4h^}uQU0*++0mcjI~KR ze2gT$1DR?{nJmUsi`cj2*W8g%(xUl-x$L1ZWt!RL-P#K^nn&-RTNoHb1`fodW-Y16 zp*J~cNwNUy(S=*F^1xD_wbnfe2%Hms&=+xMIki|op`cYcj+&#eHGM<`9{jG-Aw@YO zIwb()fhBiyhW2H>gQgo=V8eI(;^xXTP=ub!tdWDhcsV6Kd2{vB;;5iErj;RM+xrQw z2R(H~YRPN@SCe3Ufe*PPDl}t`P;chR1=#s!zCupeOc9>vDNbyXRqpKgO>TUj$~0u2 z)alHM2$_yF$mGN$yyIo*IwP7q7Q98PFkw7vnZtVq!*Ue16>q{v4RVy+Zd;}oAITAv z%FUZo9H~QoIb&+=pkh~8r)Xoe>r|N$6bO6Ljb0Cq&n=vuU0;bTY^zvPbM%ex1_H}n zFfgc>kdhdkTRZU%tM<(;;Xqu5t_^ zZXl~Ys8cy{fUfh6r4hJDY3tf2{tTc_6rXeQb5*aTN9L*UBoB3MIt1aU~@hD59s+?ek z(dOB*$0LM_JK@Q$(zp%_Zoc%!yLu{M>R3Sy;T9h#zrfgjje+_C#TRVec+~P~ySFeb z`wiPT!IWkQn!|#Z4!XuB(`%eIekwfdT5&=^;!nb!cGkvK`8&0CQzk7ZYZ;~`FI=fZ_c$7W zN`HHRk6<3}UNyUucRmApZEDL;eP*!tF)U?f1FLL4+!v$VFsx)dehnm5-eXU*DByO- zdM5*uA|IYMlFQ@mkX?>n>l0l9_(E6u z66tvW1KF9~&Is}oRj!*w*X8hwV3ECo*DPjaA~Ddd z1vAt~g!fjOMIxm(B3y??*IwOG5VO9S&C;wcF^uEn)K}COMwmfI0VII`i$ltY6!=v$3Q(oPtEG@U4?& z`m&yZy$+!MNmrJ6)dk2uRHGL zyU;go4Mf!L9xek#=Q0(!+?qNdw}sOX7KgvaP~|E;MSUwYL$KLF`~TB;+gsj zam^|oHGLB8J-P~$(W3Z{F{_gOa9+aY!jPkNLM6`Ks>PfT>JX|un}zFB&8L@oZ$08b zG^Gp_XM$CMX|mO>)mp+mrNQaSwVtH3r@-sQ1Zx&4T4yH5PLrl1zVVEUuvX)ZJbyyS z&+s*@SSy+@l(4@iVhc}=XN%{V%ETtO=kjc;w?5@#x!Ez$lpr={J1AD&(AneKe986{ z-eH_9q|E^DFa$KY-ZO0ROe zr&0CIu79yRyrx}2efh$S&hjL9EG*%5A5&&d_uhf+!hO9$fNCB9uqRsO{R>f5(vp5`mJH@uJ!#^7zghd|0cs;o`H%%M!7jO-EGB}bh1 zAWj>B*NpbaVnegN-d-SQ>%4cEERsi zBd>8__f>~-JJO;*Wlb=RqRzEidE_D(Poyo=tYVe#S;Ym6BYE$=w^wl{NLz@!C#Rlo-K46cIdvXD?T<3gWe! zJ_2-YjFcxU&GW$0*j5M>lK7c5-Ek7OY|Okoqm#Ft_VDEKQi;Db&8q8s(mPiOs(IYSrLx#$px(0HsmTN7E*5lH z;Fo4Bk))DKnSPGl?@ASApG_p-g>lGo0*C-di_-SN&1re+OG-6>EXKSWT+|U0I!u1E zov>}N_mp3}95b&saa0X7OXB@77;)9Y7SY^iX71t9VT3SaS6@}!5ed7K8UZjrmZo=c z`{hTg6N<1y^KCW$v5PwtWP+`0nA$Qd^|Xk!Q6M2A)Hsz!3XJd}w@D zF4eQSZ}WCnydh(^-6ec^uN6;a7+{=yX1Xf|3Ty@llcG<%v`E1MwYEGFBfPxaZQ-I` zI&8cN%r}7a;3D6{_aD9f$TPH5Cmubl2Uu7~P2n$#L3B87A)a@Ly%rpi1~75!<4(l# zh9PRhNMA9f+**6Lar1_i^%-;fB9@!hNUQ3@c_wQYv=pTsY+&?Qi89o87rKa1*CVmC zMfVvOiWZ_-%ER|uHiB33rA1t}v1{nd43xTdwe{{cn&Ci=gG(Jc=_e^2i^qv|Am{VA zQj?LHgyXRq<*h$y0cHfZeHjll`&qAxQ@sKTdfsW+_oOs;$RTZCXn?gepOUtAvD2#N zLAG{m-n0@FWIs8S1p+=Kh+8wk!HI|cj2{rlHA?kZIuS2Jr(?Z|4LR0nWn(e1By95a z$CdX4ALxo3Wm$-taxxNM{@h9 zo6%tO%#YX~%PV!*5Iq4bC?HM3jd!~ZBhxKNLmE#(#G|;IV7cA7Vcd^Q1G8Mlu%wwu>Nw1fd7v<>TVYf|FwQ0~?Apvrz zQqz7-feZ9@fOofV2keE#C%q(J^jb?n!o$$VtB=IZaKmqFnM@yEBz8LTu(2y+E_sO? z9C34UdNwvG085&|(mY(W;p~>W=q1#)LoIB$e*50=&_jJ|HW|I!LF(s6b+JGt*}*S( z#+G6k-p;axCd?UkO~bX>)qCK`TPV><8EAo6yAnVxub%_0#R}*xH%M=(61|{Q1-^8O zd+_KHks0B=7WaG>-Y^!KYFHb`@87+*V3!66^)NJ8hJxQH@vtA}q4G5fHOT6ii?e9h zaRmek279Uz#0W|a=0cb77L`~tN@GGoq3Q@z$ZJM7p<8V&xv_zjCd`=DkSPLXcqO=Z zgMxyhva32dYWBLHXbg~Zrz47?ki+%d~*djgf)?|Mwi+1Wh(4yJEO z3r6gAp4v# zl&<8C>OIV)C*;2apGB-5g}mwT4n4I+N^uv) z2OFLHfKLIilqQ85vSY}#J&|)B0fLaoVKhUrcaIV5xhM-@C){qempYJEwXHSvbN2{; zIGmbnJc{EHSlXrFdY+@C`EbLvc=i)trZ0RI>rK_`rMoSmmx0~4O6WHN7Yp^481`;P+yRIN98r~p^7zVnlRNfVc*9BbymLME z#;{SC?+q*1(-}eseqx=|sZWsM00{s{+E^e9x~cEU5wV!Xv1|au9&Lp<@fa1Oz&oiF zJ51bdK@(h;dGFCHjeTO}X%A~J*bcPT37*E~fBa%B$2}2ANmy(EHbx;%BWzF|Sa=7E zxTm$~Z1vCrx?mR7)LfcXcjIVnO~bhy0ZS3!I8fhAdHV$QV>1v7&T2IYZ1oQjDSJU0yjM8SepV zh1@zQ9KVEWlvUsDXDY>P8Ou3@$ zS_*&xm$lG;7`oF}&MG<(HKz z=BCNWFznkcVXNZlTSKnC4Uqa4bs1h`vJSFpdBxk9?bS5Sd0ic^7>+t-Emq>`RCY5i z$f2e;(CQHH04iAS?HZ&=%o4Pw!Xbe}yqeiL8|q>Kw#f%xsXTYD#u8T$^et$WzT$go z7%#RgT7bo8?~&J~v(ss{PZ?h|_?m*{I8-n6!|M=W+s(p;_DE{a69B3~HGIyJ0eF`} znIv&<^8u{gNfF|`0;L_T*(eg~@F&~HQH-AakPoLB%!D*+pS9%3JK^oyX}1(z++Y<6 zkKUKF`Bu8PFA(IlTx`zk@Z2!|5flET1b~+B@RMJGFMgdhVuDZAaGkIQFy#@uRnwj9 z)ky9bj5^`EWbTu3cnB1ZaJuAne)vqWhu$Q-!YyH^dn!HHp=AD?b0CP|RDoQ8AB=?5 z7Cs~)g0sBdz->=BRbsp_C1GCeW$~y-@1gZcZsnVvB%%7Mm__9D5mlL1=#cG-C{|ur#yJi#o;n;-j!PkZRi` zn4nLVo^y&JgdZ6zGkc5zmyWAK`x)HWZ78=Iqp_AnfUumm`T0gRKfq!GXc~vpLK+l8 z7smh@Jk)&8`W7#rC<0f6UUd5UQYRR-CLi9ki`Ny*O3K^KNyJv{C)|VDOm#sx@Q#Ga z8QjdkK#Ss5w6zcW(l%qFIK7k+#SXQXGH;6`g=z z-g*?p%<0VA2Sr}nl6ZVu^QilFoekF6Z=Sz_Ov>X_pfOQl&MoXadFyM>!ddztDP1dX zBRxu(fvVc1-Lvdg9?~&7Ca4ABSvEPgKU&@*)PPo~Ks|PR=t7z{L_&>RwSjwpmm0{s!2;0H)fgQ?icHd^&16 zgScm0@nXQ-H2NKoHF#}G9vY5oiqSZ%COVp2*RaIRiZ}B)<1t zTF&4>`vvHQiCfX=E%K}N(T55rulr3m0OC!=vChfFq8?oEdq(XKm9z1g=#D*gEcT^h z@rZo$$TGGY`)C!zcQo&ik5nWxF~OqOlcCFOvBCv_hTxt>!-aI{MO>f^lK?%z(UvjB zim_G0VH(V-!NVB~s`5DTwrV-|GK*WOp&;tDWOOl5P-db~T14*fVg_~{YbjHD46Me} za}ZHY2y}rub6gAIvF+7cK+$U$PqGD*Y;5|SvVf)>XCOms_vSr3CojHt>WFVko~0NW z3-Ot6Hw(Rq3~375do7J)ubB82^(;JOj*V8OM6gmjWO$*}>OIOUy{_|KBa=6G^Stt% z`0Na==*x}UHBbj|Nzh~TfHzkGm$?-u6O9I>k#?@cdGEFNcpi(K;EW=!SX)H;MQXZk zcItZ$0Gh|h57Y_9L2;ggf(=kY6o@?F@PQmA&Z$fDBv`ZcJ>@7XeF7{ce5!js!w>G!t9YLTt9=pN z`D{3mG&CKbIT=0xb8&mB@`l$>p@|SK@&c@3U#~)m7@*tj1JReU{$gL;$Y7d#KS-9x zhVAkyVa76*f^GAB)e~#!w6Pz^5}??yS#Gf_*G|kv?QN(Tk(iiow4baV#)lZpol@w zptt6|SwvEPd`b+;nguJBfY^pAXG&S^j~{q_t3$=_R%9a4JDz}`tfr6RiV>KtE{ z3C1JMTER_Jin5tD34$5tuza^%WT4Uz%LrmxXPWs+t>ynfFq6N_aXYLB)^eW8Yl zL=oZ@%!xAP!?$#Bjb@H-DPu{J(brO@481I!of2y>4It1v!MCc3BscOkcDKj#r*Voj zFXOEY9w26IOD`DSg0v31C3P0 z;}mrfi%z%i$zGvk+ZBGb+bWAoF!UpFd)vLUB1%MCjfQGpU4VwBF8-zXN)MtfF z!?5S~e9wnYOaUH3WKQrs>56^xYVUPuzM#)pJ0S$=U1cL*t+`jm1;y+ejLTU5nxs&yZ9u?4)n4zOhQVAc z5Os;gNI!okUCFH?iP?b{djByT93#TVnfDxmg^IQfAY(A)Ewo^0$Kr(c5?yFlcxF5} zxfzOl+c7H-6PtxboM8y&TC*<0&+A5d?kO!m-J#;VPLa{7ec9+4+l{D?Rf<403%oG* z6@wV@4-6Z5c*N<{wAl6zag<)T5_qeGAiJYB_ZiVkOS|ljrNre?FQ%n1L0xr^RbQNT z(^tSGHMrRZcRuB8PwJ^Tv-nuV^8>MXiG#yyLoq-Q()3;*u#6M%w9EsY^UZo7*5Zhb zezYB6^;qi$n=+ZfE}Q2H|om%dWoJ3H^J$H$v^Ci&#}Ot^%yicQMLm?D0VS zN%88`+CG`ULxet08QtuDsTgM_9Y%$i;tNE5rXy3bvI-W8iagF{(a-JXT=GTkP0mw6 zN$YmnblKv>$Ls!vgBXmltebMkTD60{t)0;E^f|$;`)r2gCKbUlLsK+Fnkcf!qryT4 zE0E z3j=Iz-&NN`NnB2srQ1l%h=fmuAAK=QTHevXv}x7znD5#j`c+XXaLT6N!`Vc9h8|}j z403R1zHXF`@ya=-Yrq$B98a5^eI@brPd$nMd! zLbt#S?l+J=>=#fo0Zc;(rj|!a6M5nk^WDIy&oE)bdFH&iT2%Ry6w%|~Fu4<|Y!vIVMHWvF<7QA23yE!}vHF6h>0)8|+ zcW*FB$-!tCC*I(@cish&y@2TNA#6-jj|+8sLnI8|vIq$|U+>d35itYr+!xk(#;zA0 z*TKbjxYKMk)V#;_a8QmE#EX_8_2>mU-0=;RGk9n4$>2V^ccT3mIm;Mz@SrgKe6-{} zp$*xoVK!loXNDY@PR{g1_98lqf{(0yqu^q!qn&DZ7g9>rc6g8Wap>&{5gpIH0;WxJ zVUXty>?=BK{9By@4CC(&gF` zq9^E{@Z5r1Ee%N)1mMAYn6e51Y;)LABunTT;3>Rv=a|!P5JvTR0i;dEm0EAwC~81q zPe%2wTh}-ltG>*Gxm0E_fJ`%2sk8Hyh@r=tHSQ8{a}@kVTnR5C#&h1p1!MR zxF~PC&efhWPuVK8C!U8eLWb;|gUxuSB`gXyT)vLWwYFT-L+{{prC3`nRjyM#(pp-F z40o#GcPM(>wZ$Xyl3P}+36-Q-b{@_wGEn0xs()teLm?-7Sb8E&lTJK5^GnCi#+P#GuY585< zjiC#$9vowi!@VVaSF|49u{xQW1O}Y2DR{XW`^?3}(#cvB@E~7$8&RJoz2UbJz7r}3 zbCYc8)~nH;KF5=$llpWHR4~Xw*bk=R1g}`YWW}pxZ?}W1(m_k7OrKGvZ>F@HJo^RLL7#MT}|LH{i+@Z)fJ29-a9D@(w>Vm!Q;A1J``v`p@B8* zS{|GOD?rm1`NT`DJw_|t#cb-udSG9^dZ0`xV?Oo=)shJR^ELwq+>O zBApQz>d;Pte&=qB77BJ=wWH>jo3EEtk;V4<9^7a%l_i5ds(RG^+NeB&c8aHH zk#&x__B`_xEbm!r?e=Psvd6vJB4_LZ9kHtKxI{a)gTk?IikDy)1oDtGXijiF!QUHD zjk7zQnChfcPFYGXd4%4V6P=dJ9(05W6Aib4DF^hbNy9bd{1@+%KvUkKZo) zk;m)W$jT7*?v05@+hjDX-posDN9QO9OJ4FInsyMQ6f~qZ{4}I{sNSd@@HfT&MX{_X zRSF@!=?=9$IX~7J^DZL2dR$ul7PmD$yecUe)p(y=-f`Julq11yiIaR_xLgB-oa>db)m@nvzqU!5zhlUKp zfU0hp_qOi{+pOlqO*BU6UAPItxpO+CRyR`<9LV#k$asll*LtC%xB^E(DTWdZ11jx` z*#j8e_aDZ2a3{2@Pz-PcWa3v#)8IlJB0{0&tfCL@`KvHxOaRe-h8`=*Y{8CQuOO9Q z#WrXrk-gZkel@RC^##xLxFMjV3C~ zO?j{~#L5a{SMCV9Z{`}DIW}TEpF)}7I&Nl(C3@%bUZw=UfQ${H-LeSSf<>topX=mQ0tLi7gLqk7kxf_JkGhVzkJ+RY^A8I88I62PJ?jll5&<33EE zrPs^Dq1IU&ZK>5`ui=(-s%A?cK{@ez&r!GOz0!~ll!Mpd;Us-f2nIVaQoOxnFW53(OThBmN#?GC?QHDWz_OX?Cw zwWe;v)c1~Y3G#7t7ik$aaO0HbKBNW0sOmz)(5Xb(?~0z*N<9VDLkb9-qmVr2mSE;|yA zuG^Q3WaF(6i&k*yyC;}^5*O;{ z7I41o2K#V}3gty=Z$j~T?Tx?eJY^$c-Q4a{I(EZXZLLjhOBS1hS7WguIFdtvG)b*R zSbcTjEBRg)<>B#7GBF>B4g2A=|HpCY5ZcHUVh2E%vpu<?VXfGCES1O!z8V`Hfr zPkFKSA1@3jo< zCuqOW(;J>%injou~d<-7FTe6&DrDkQI1}@;V{c@i}#> z*CvtJtGIoAdJtOtta5H*tDz=$svDeX9CCZc0wart(ZnW%tXpgnS2=t}o^Hs^RfZ!) zd&J9-^i^3uC#r<2*$(%`PzN=T{{OKrHVRtm7EMOVhQ>*je| z%X+T{{1EwN7gF0eKkN2={G8rl{K_sK&&=S*65SxhZDN7JIE{P=+uHh0MiV*Nm&{NY zsEL>1$a5jgq9pI41jgpFOtLq8U6ZP0MYlP46`W5fh8az~e(vSny1&yOTb&B|C&0<*#ll$1su8lZr;KR4^c+9KJYakS~rmz~$xIi+9yfR%~Od?hW~b=aD}5PKC00 zL9Ddt6mnW=Dv8Qw<%>n`<8q46NxL1@lb#k?=L4&!uI_jN>ayjyI>B76>l` z!(YS>iJM>;Hn>)S85`vMWHyWA zOPdkagmj<;WAeL-}2fcBFfv6PUJcq%%x#&(HD`kUhZ#s~T zf@V-BLo8nh*YXXwA#Y}=zIB*BL=qcTk(1olrn+LLGOegcLVfwl2wEtfzQR#@@bGp$ zpdWA6VGSC?v{s1`?nrCf-8;dP>Kz%zBGehGwbW3m0h^o_N>)81H?*nv^)z~0Nb0&g z_(J@UO>$o?-U~rae{so=_G&cF=$ie-RRSAPU&tG{p#~Cz!FjAJxV@N^6%Q`sxlG$T zxYv}NkSYd;#RwEvrh^%24pDNHOL3g$-PF%v000hNK!i8k)@5oWrdsbr+*2}_Je%uv zrU0x6N40*u0I%Px6TrU7lC^4eD(jK=uDc3VqW!^*t&uTTzzzcMl)QNo1JWEr)0sRY zFF5q=4bnWX$EQBR;O|7unS}{SmXSsx-iw}GW2RjncR76+1Mj)zYu5}Dx^2TUbCwHl z(uH)O;&YiaGioUi8cD}&?DBMRiSI#v}*dHK`EoOjV^#vTc-N z-s!GcZ)Lecv6UT?q00`L1bi4TXSVf$1C(#6XYRz-c*2;Wk8qUOIV0LbRrA!)H#8Np z(A3ixPB?D)FvHr2Dz$&Epzu?3+lN1C<@z+ArcLSj7Cl*;;8IFTMTCZWFL4Q1BX%IG zo@a2XSUh=YgV(aWT)BO|$E*$qFseNlNU~JBQ|>73r=$3=&ope7HLo|Mw@Ghx%-=hK z$(Ev_n@rg7Yw#)Ho4n?fqzoTeH|>aeW~a&$W$p?#uO494@6XO*tTudxpVYVzVjCjT z3{)iPUJ_n7$UG8MamN75SLdilU?-V~Pg$IhT1DAw*B+OeIw@e91{fyl6Ork4X(9`@ zjaWrek{?BLTR=zCW?J4YM%2J$@%-}TOCNkVBHFu(kxb+G2rptzDGa3!Vuc~L4wND&h_)C{m3dguutRPKopJn`_5NbFG$$~~PfDu@vyK!tF5_(DO$ zA<4y7eWr*u;}WiGIVa{WGlfoWPcmkl10}Rl9wco!j7HGp^Xc~W;KoYofar4qPxNNo z%I;5+3BE|~b2Tcg9S;v0do!8Gvs>gOCTbgxm~5)2w2!aIBRc`td{Rfl!=)GA1fE-Y0!M)uiD129AckF{qHndZ@qYwb)|(U?fs6m$oLYL>JS%i(U4 z9F3wlA1y(ws7GBW#$_v(3>dBhBIz3FIRsZ(#H&W*QAG{Am7hwveia#T77pUj3r!R^ zXio*A=DD-V0n#$3Md;H!ztlLWcEFx#0Z9~;|d_#|p za}4Oz_x!;Cl5L{r4IV1JU~DE#ADhHF12Y|XF9&tcP}L<#V*B3kBX8IkzJ{pt;j4-v zgG;$Xt{Rz2b5;!)dz*E671YHA!g^%;w#oF~61*p3i|TNLPu}{&c{#Wd)R+OWVBX2+ zVSI156kt7*f>9n%bG6t#>?hIW zxIkrZ{GfOMX;wn0u(%D9G<03GQ&fOlJOrN$O{BFvFd=DeNO)W~;IMj5(A~Qc-EzZ^ z3aVP6>7Ui|aX0zAWZkdPYK~6qXP? z)is+GBnrc#<%m{p*D47mrFuyBjk15COxeqrRgkny_CjRno-V3JxK2G*Y5S3eKJD^A zh_92O9y3l9b4v=7J?FvBTr4?wV6b=beC3JNLBD0_F$94c-KHm26mGor4uQ8O4W)$w z(?QEp+6jdi)FeUmRX+q;sNo&rhe1mBgmp}Y5Oyi^T3u@c_)>5&%v<3`N7-JG&AkL& zPN6<{Yif5vvyThd`n)hayrGLl^))n7(|EaTAUfuWz3QhKga5L;_BO5x)Z7fVQhcx< z;5;?ui8qlU`yMo_E*+oEM7`#oeh+V*w*A%KN@fvOzBZ4`K1B`gqUZP0Q^v8P_l0-U zRVtw9Zow?PI6z~go0UK#=z`=X>(bFHh;Z2F{GPPmP{()xY!w2}>$sMT06&~66EI_E+>@|f)4XmwCiP+%`*6uvV&Bg@MJea7H2r> zBiF0!D0I%0*hb&*jS?870-_TrLN>uJTJ&WGUg?16lg=h)Q@*sWVYI3Q8+T@X5f`@4 zA_&RRkc5#}D~;t{#NpCRt6ExYaGk9QwtP`m|M7P-B{%5 z^DR4gvP-*Hwbn`l>xCe83^GfdXX27g@|b?MIfpX_OHm%vbp!=~eW->oQFGWSQ-YWf&ib+`%{m zOpqV*O>5^a?MFyO1|BU9B&GZJ(~e^Y+Y#cEPs*kE<$ zI`WRmZ6^uJnbl7thCrn1K%Fnk=lQwHEu@B_GcmO8`Kk!nQ`kJ}mZcc38Q`~%W!$)_ zCZ0!0lQ4uK`#pQq#Kb6jH`k`fLT_pES)i4Ycre#8(w5Gw^gVEpD^kzaE2mK(aC|S@ zPM#XzyF2|4=0_msrgZ!UN$V^~ManCo-8E`+%~lE|C)HfrN}3zXlby6E$Eu4?MYyEN zb50+-U*Cyir%?n;XPrM{m6c=;p?d7yBa0zugi5j=ita<%9eZst`w|~i$CDO@3TIf- zxiE-V{9KdH%I!E-L!T^8G*#g45HXGn=3b3X@AB$p3dy687#NI64~M!odbWZyV*oSv ze4RLWcQqpy+Mhm$hhz52-8QS%p1xGd3nx3%eVq` znD)j4O{|{GcxIq2JggJvJpv@vHFG3#<%6nLW zC)VbtZ`ug8LZ+b#_&lIyfo24Ib>RHOS8j51@E}1aBSme}p`1=+FO{dlBTlH2v9~AY z^;+sIb3K?bp*NgSjP0DWi@Wf6!M26iZ&x+}DE!&2ZKuvYTN$P@Rg=BWWU@R~jGYwQ z;uYa^-RJ2`HiU)hOrC0a0tGJz@hn zO_)dTK`+;&W}k>d^_CCBAQXh>xB^U+!Na-cM9H&$PZ7s)In!Mv+?s3CPAo1K2tzu^ z&3p6|O^noD_#FsIs5A;o&F#cx7Y7u)VWEwnU|Tr%x`B0(wz%(L-l`XFpjcy=z{QAH)&W2v}PM_ZCd z!iC3l+KEJ25+?5^Eufrp;dVuhv?R&E+Lz6G6c`ga*acp92%(_#iZRN2%_9<`4+5=9 zunn$3vyn7;9a-#YOegf#%rh=S366O6o}@a-(;+fEyI*9e75l*~A`(BfEvr!9)g!o) zln@G5pC`OhM)NE{hWJJ&JGSc%#Z~I)&7@-#pOO$VMS(u+=7`OHEc5s#s{B!)`LZr0 zOTfIkOpaAv6&4VExh<{jCk?k7%mKxxpsn<*J!-SLh+~#q+@2wJ@{E(hdcT`fqJ?$Yl$V@fsxo(H^(K*7 zBK6ohMV>^o=%lm+&NU)?kUc73Q+RirxNqT=@Kxw1%}QT17sqWmId4uIucJ#T&S@^+ z2xhit7*|0p_JC)q+Z&%iXtN-~eN4h^U}9&gJlF}EF6+}U3tA=^+bq!;A>x3+1K2@I zdVIyxdj+1?He?~Mrs~EOgHO2>45AGM0<@LW%&pW{IT<7MRTwej&W58z9?vqOr(;aY zJbPdXLQ=9g6bJ=7WO;^z3R_)b8ahH|2{rFbu~ObZzrz=ux@GME~J!q$Q~`t%7BMY-sqDOpDlXOO*bzl^`Q@pX4mt4$MBpCUnVVZ z#fd#OBUnXB%am1Jsu1OuPgvh-;~JsWRP(9d3pLImvG*yjp`2Vtz9W35z&St z$6P;->!(qxFO0{9-azRP1o!C)vAnbiXIi|i5`7k|m*^WjQv~b1M@L)9+-8TXPY@qg znY6?qdA@>-gV1C>O@fTD zKguJb+u%r=r#9C62(aalzj>Q-a0ZEzPQMG z__AFb11JD&9*_90YA15?aO_ZU4&=3Owx5q+JR~81@40Uyx$UqX7&DmjEi(wEk*c%y zsAR%xKr>b-SG@3}97?d8R4a5i#0o>JQrrnox*e^ii`{!5mh4ZZ0#$fxsSY&-2OIdIXwT#Cd z?^eEXHu%Ch!+BIrfgO0gUqqbjRISqnxSSB7a0b)NOv_7r1@+V<-|@?cf#?joUR{dwrF~WbR5;N zymy8-Trcc^63fXX38n2epVR}zBC>u&5V8H-ImM`)%kpLfAV|DCiQgIMttR;G8!u5N z6vC}ado>Rd3Sg!#q%!LrYm5%=CW0RM^wobn^HX-SYZ?#*jmsbvq~SZbK~i}nv(3H$ zhY*&>HjvMN0~kC4;6nSN`nI>FH&N=1Ll={@+8U@u=ETB@4(B5~e`|-X8V_*Sa$#S( z5tZn4TLdQFq0U-8uj&fBcm^Ivmh5{NunTPvQz|FIr-y*@jE>pPXiM*Py+WXkxUBJa zY`Md%u?182IOb%xi)jedR z2bT-O5poWORD~315uEw>h%VQJp36L4?9%M1Zi3`26dyZ2d?-L1h=f%Fa(?=3_d2o% z1Gp}7s7JPtzz>C|T}*hD^uY*NCFA*Y9FudA1)h_&+J_t~|o7j9Kr~&74rko@9usZ5@ zCNT4S-coj&EtX-MP!;gzQ?|_2c#gPHC;3X0cb4(um00S*o?^noq~buILn~BLj9h-t zE)I9Hq$&BmW(wLc0cVY2Lw1qTEe-|%avsfL0Zc|Oq#Kkc<%V=rrEq2G6$`|vGH+(d zTX<~TRoW*8Q8ic_BOGz++TX?kJlcRQcXRB1O7T$6uRN(^+|^#rRXo`x(2TlQF8=@i z?3jtmtRUQYj}6A8AMo`V20VFbXsrtO{55mx0YKIg97EeR}O+RDUYDF>tCWBNq1Ov0S|7G2vUdN+~g#X^8AHE5{BdFXWE4$@%H z0gN(?m~yG@#r64cbw9Waf0c{^Y13lKA z9kd z_pMz**hO#%?)BY4v8xFeHi{_Jcm_Mi1Fvq|G8)H4HhjjI3imyp;0gojGFH*I%nim7 zIFvRr#FU(C4f;vOUV|L}&aj&dsFu03?_(D4mkvmAd4RDJC<(c8VHjXP$a$I=i z6=iJfGWU!6Wup5lBRU4@j$AwqzPyp5qacVvmn^Z=!Be^J@=dsGY}r?mI=xoix8FO8A;embS@c3Z`*(*XO>^h9KnH!I&R~^&4TBTI0@ysM@hRH8 z+iIQ*ogEj^?IwdtF;r{!QbJ#Ky4~1nIr477hrxEQwe|pfUe2?8P|H1h@Bm-n14`nD z1}~h5cdu@DUNwSm^b775x?py!iqVUQz!FiD#)iPasS575hj6>h&~mH;DFesW*a_AH zE@4wJV5(eWNG?uD$}tOL;i(MMu?S1OtV)7le^&#%_?ylGxs_eh5e=LFu0<%$z6E&Dl94Y z^g{@GGx=O*w^d$?2qic=h+5dmNTn>yqZs5*qFug-osH}VioyHPmg)_?0-j$Oi zVQ*f}$b>(;_nO9`;beNR<{_bq^J8^-pxsFlT0<%P&~8n3$Obg}O3jMdq()*F9=@6h zN0>nwzZ{3DnwHKhe%%IFa7*cMB`R3n8KW`1qVKV143D1)=6UmtO`_2>+BINiE4`Ei zQY{%`Y^YT@k%K9yhnxpIVSf7#&Tt2uq*Yr=$yCK}YbX@PDb0#q-Jk=EZ>_vih7T(vv_xcMTV)GcMPo& z&Z1AUxL6*%@Gb?*SJaJ#-_tNRtv&7b$|l_KJMeZbt9UNr_l#VPt4W?7Snk|AN;j`yPD zNOeS_SN1fo6!kq0)F`Ixu8e-YlziJCBmpUv$6@mNMmfln8^;Hl)BIC-qm9k^g(ElK-Sl0seB zct_gX!Ct%@1R5iPS#*>&Q-MJEWoBQp*l;1)^(da|s*$EuXV#O0wW!2v)Gp3-oBi}!S#%2A?%4-vE$xlVBj4!yTW=lp@agoieK zZZPW|;VJ&w6XT)8zVD0nku zu$d-MyBu5l6Y2Oq5^jwgrpvV#>WV0(N)6ChEQ8cLCPT}d2~d8)55aO}$!B9cvw#Mp znwM@C#}rj#DNK}CrZR*dB+6734UQ}U5d-Z8EP*rOUXLqzj>n2kJI7|s8TzJkNLt-7 z_`XM0xzHHZBehDCM(Rs@vSFEoBq9_iv8P?)Ht&wLv$3-4ZRkS`k?JnWpJgyYgInyS zqihxOSUg32XL;IcY+L0O7Idx_>8oYSha`(&(D>jk|7AKgLV>h-CWN26VAvf*Lj{!< zlwxTNGjes$?=?*07z&Gus5R_udJC|VC_axuWgGdnz!6e5x=0}TOjcJV-TO>VbX~bO@ z=xBWughSV+v)O^i3wtCNuT#+n?Hz@RN}dyV7O4slc}5o24eUVsK;-I+dWgmz6_shb z>~2WM#Dj}QZ_w~tEatSPR`D5IjSXXCQ4S~$i7VvY8jAq$0dz# zLHa6Us@Lj4*Ax>XT&PGOh{S{8%qyc(Bt9zP<8pf1LxaEq=5CT9iDj%z$2hd66^-#0 z86(ck*d2WEO~>*CO13RL?^%Pyy_kv%S+%A(b`|u@($mpyCOIfvs<`M5(ujxG`jTqU z%?(5sBsrfn8(8_y($Y#S6(3-4Nr|GZg|d4tGxj#ReK9QnUrcLuswHqDKbM+j@|a+g zd$E+RjYQ8%qJpL%CsU5VMuGeY`RsYKA}k_aOiN?qd0BShOB3WwqG#}0QQIgVv*WAO zeMa5(#P=@{Le3*Ptk=?`34nduqkbbL8tgH?I_)Gx6S~)K zBdwtz?Q7$EI!gWC1JmfhTtvdn>J>&G^&=CRQycVv*pBz+>ChX}p}_42mK@1j$SYxi zXU*u|3{XyQIS0?q!JW}3<=Mb9vs}TT1|P|K@m5(xiK92g^?@9aSxaQk+p#w!laHD6 zUPK8L17?o9^8-F$w=nYaLq#@XSf8BYD(X6e>?3HOUCyM%rVFK6wQC0LLp&*9?{z7p=P!QqB)rwMA?b6w5+;j8uXG@T$dR{nPlB3-l2jyWl ziby`-7M|l*WN#QvfH+)FPggvN-$@y}@LPCtDSghsFsUxKCJUUzJG6_mD`aAW@U;8_ z%MHgS)2pW7U5#4yqg%J9z}VOWMwEAR9YpVBy2^x5?EDP{L%9Yz_G!)=%j@=66`(on zFWbxWO+M%~vaaPtUPRSL&tcf7SB^6+Lru_y5TfEKMEL_GE46$J}85~B76YJ0|YlK2g-HS`TC&c5yQ22WW2LDpM{YKHM=-KY8H4mh9 zyPk);;G0rinj#fSurBw6*%8rTHJ?zOuZS?k$<-3gqXVFG5{(&UpVwpp$Pwl4B*tA` zN1`tuIGk@cP=X%ot30doS;lb5synkBaO#TCM2C5zT62|`>0D2GLdCtuPy2e&wu&YW zd{ru0kVQ>=;9B(okodc`g8@NzbqnvzxDkBVee4d`kg-p(uGFkD;4O01^TgOU(5=E3 z^%%IH)Pz2is8&#AC=yE_L1PEvaxRzJ?H3u?LpU5x^;VxcJ%7{Q(cABlArJ}Kdx=BpSsviVyJ?{iyn0oV;(Lu9B0|X>w4v~Q2CCxgcHupM^ z1a1}F@i|`~UBtwx#qjbm9dsVe8v}iJW#~NgBrqyQO1id?v;%R?B;2<)GUVfKOeHe!u-Wl^p9tl{h5~tfulfNo8JmblhHzOz z52TlyAK|U+E9I=?Y&H(rA6%K;;Nj2IR)nHw<_BJHr zIRS5uHRwxBEeMzACBKoJX1on&6)~rz}8`PvmmapC&^4X^32H0ZXqVWbkjKX^g z8P+wcR~^a|tIv7}Ew2NZ`2PKf5;2A(@JJ7Q0|0ld|>Yf3L(UEq7|;XNLT z<&DJU=w4H_2E!!5CmYf%6?dEf9{fUc0m#7+6gC<-m9w9}=P#LQM*8V(H`dF>wMmAx zNU`aV_Q&Q;sIXi7@Tt@2sw-Bhg?5*qt&EG&fru~6LoA`gvP+oC5!kS{vRHdn81PCA zrWvFB05#C<^Zzhak3`W*Hiu^xe62ldqVhg@y&T*;Ir!y{J>@ zrL64=>1tOiALL<-t3L(&I&cgRmy=Z_W*7rXGTL~NtO9cCRnu_ zt6x^v-dt(5dpzfH;Rer~wwAMIJJUn8b;XW^3w$fd_DXYt$A6JXoV9!v+@qWmkC^`B z`rS}WJP42SgUL~H>+#-qx-YmK_WX<#?4BlKTyb`B%p>cmwUa2f&^Vm8=uiaf$>%VYg6*hHHz8L~B1 zN{m36h)jofYNfr^OUH8)U2W*nu^kub=xxqgs2`y0@Flm4q$v|_p136KQ{iDFqj%KR zlFov9$(@E)(epOF2Or4^Spyp83idiaJ<#x5$=tx?LmF#0jRNC4ZIjzk@g^{)IB1=e z+^d`iGH8iqFWJZ8t;qo=XH2e?nDlcoDitk|Kv?L@(Y44UZJfCYFJzGV{NhewN)|{Gfk3DjIqbyHFRu~O< zAHMCEZ}4?^zGkw9lynZ&e!GnPqDZjmC~JYei1iI;ObCoWJxVo}E_tLn9dw77^(I@! zo-Q)7G|Am7P;a`34p$c-e_ad^W!*eaGj(4yMVrsv#Dp$C4Jre+l-_M!mfiN*_w&0P z4ib4c%0>jE!;(GVb%<2(v}pZ3YmipP%f&^~cFL5R>iXV6>M ze6k$-3{sq*IPK60zZvABZ3LmbzL@Q4UVm>D?gYVd5K>8rVGx6WNzxHQCK+e5OrB*5 z9YMRea9?AqtLUx+E7c4?ff-A*7nDomc8Dfe_3-t-VDBXbM`=v^vC`}3%Pxg|Nc|RF z%wIxt;!WIDym<5Ikmr0cqA1YPDxGd#2Fe3Af4dJ}+xc@*5t{APIPk^M*%PmB;VMj% zCo(bv1np)F(Ff%WmlAOZkp4XQnrJ;9U~Uo%<8(jZEDRY^8f zoc-eTJ%SVr$W=`>mONYGo-2Q%u0d8Kq2XBfTIcC9VAs@)Z=Au7tCHBE}}23q6k_{!qowHJlQS+x@&pNsOb8LOP>-x zemz&c5i{j@?&H`GTMsB(ryNlDjo9QdRt1B@lO&uBKl6w)a#)dQEY;rP1yP~_c7~T( z8z%J#vY0XPQ7U)u;gfVa5IZ3!7{rh_Sd6xF9MEo?wL}8G0N}@A3Hy#mu_D}i=&oAA zEpA~OIGH^$eId=5#FF1^BI2x)1co6>gdq?SkCA6RxO%5tE>s4`PiO4Cm=Lnu%3YOb zMrC2NYzPqT+|zqDUTuunjn818OBeSU36j38MY#!8*mg$y{d5ipi0UEUi+kSFC9W$i z!1i$U2=;BBEHoI4jLM?E7kG`Oj+qTDdm%5o%Y^oMUwwit-v>r~;qH&bF|KkrFN5B& zzW2h+U@aY6X~H)cm<&f#(wUgf?x_dMC}cX{#S)|j+1yrUODM$v*j5m?<|>HqsK~ZF z+<6$db7E}F&Q5ZH`Yejq+##gRsen*%br*0Ip@gyBP4mvn%8pK>%LXYXUu?X#q4G;> zo8&WKj2IfM&Qf{il#V^tXL_cJNXUWnyt;`?YhZ(D+s5nSbttXN40-MaL{>8HQ~($G z)iray2iy~3gqn!N{vX-#f@qx{8kf7pL_Sl=6=m8ayYNsWMI}Hwlvj)E=yMKa9urn# z(O$3t2m?Yq8IWbWL+z{y*eT6!Z45~??plg?NH1cuH^T0snzmd~Co`N*0#Z@iS(>>^iS7PNX|CgrKl ziO!ajk=qbD@N0W)0kp#!d9Qm@i8fZ9Qq2MXdqfnaaa1BRpvFv~pkPGx;8G84H81>+D*8@>d(%czVr7=la!McY!Y z(uCDCjRpb_eMlh`wMMi|^?|d!*XT|oe&%lzHr)!en@^V+W~93Dt(F{h%Tb%W$2-R$ zdMVz@;20F!$G9#Tpz1c9SBwO}c=t4GyeH@Nt{J=K%ZHu;6}}q}+$7Q7rFhj#zUMjJ zgrEzzMNd!)%UYvft=?0JRUI{KCMCZ1#zr#oT@#u}i+jTWC6T|z_}@g zCF>!3m9K?9B!}CRuy3;clk8`EtOUbQFU2Fu8%CEkRRGKv0+VDS zwTjc=ChvO8js)BBa)X1gopM|vzkSj+%pNtTDGE%#;~*}^mDQzfZu7Cy?h3?d{w)1-K>(DBrBB*fVu9yx1YlIcd7`q{madNn~~ zzE2M(T~1zyMZZJpQ+$B<>h11$y|x7 z&(leI#3%*O*FDPKBx43ttK;(tkQSz}mEn_k@1Pq2;#Tqa6`jY6jyKsI)IP9qEt&ul zqe)I-88= z-J4c^R*Fge+5}kR5ncvoDCA2#c*kZ1*Q5PH9_|vqK@7Hf#x8)5AHtRQt>7g2JHtV~ zDtuC+cnp)6cm)U4R4nF^kF@aIGK}P{ow&zaC0UmVtnN7Q**x^vOP4%KrpYG`IT=o? zFVdOrN8jzJt`Cf4K7>szsQ01|%-(iwoI1T5$T_>T37@@bNIqVd(OmMeXT1-2@xWlE za;0H{@m%s*(|d0o7SO@m@C<-~v~3lW31w_;42uC?JuIy+ zSei@m?0_V0GTwB9rQhqVDW)WsC_@y=x2ip;cZ%tRL(Ij^I&l^wFRN^*RM8?g2{G-- z(;M;OD0AaITgl1wP=c`R_72u$s=QpIo#Vg~~}hLs_b zTL8DP@}qZ_L59uH`d*0(V9^Hbs58{CH?p7&#zon7*b(c!1brBWhu(0U@!k{IIPH#J zqqES9jC=8tIq8bzrBSu9ip0v@799zN*Ay!4dymNAVn5k7`C^+Fl@-R{*j8zKPQ(Z) zYTx1uhhXlPepX&p2WkAsdoObcl#IBcMuX8g5Glot`HGtWWZ_A)zZ5L8^b!wI1JO1Jg0mw;G53n{;)ZE#-sx4pd#M7W5U(L@tGPHIp$g8z zZhQ$p=X3>;i#hrZ$j8kA{tV z^En#dJ=2~+&cerDAuwp2`ybra!EX#8w4PB}qy?hQEIv{!yMBiFhVb#i0}}AM@g&p) zeMdTv;Icvx>NvK6dCa^0>7#zvUfe;@kDwu0?!^2d9@A4^UvuP%@vLUZ=G09-Q-~y5 zz+?}OxP$;DF=e#c2MSmZdaDUbd-dJKFY#o0g@DAgq2#ebEG3)`Ribs}Ng*Elng!t` zO@O@f)ojHpria1FaNR108P2*|C-D}lX31jt04qo0W%EJ^X}y)v(PYtzo+dSOZl1@F zsH~VzN$2JW5lo-#aAL7XKtrPEW-1zC+ZgbuXD$_v$LU)KTC?+fNK_{+jdSdGQ7Er! zWWZ$+gTgyn^03-CAhQW-a7x#FGanloUhPZ_PNI5?!P2XFgDl~Sp=PxWD97;(pY?-&l1oA=@BMT!e9p7Od2VaN;M zj;T`bxu=~&@t$fukhQ=#(ybF#g5I-vcq5oO^j>CBRl8NhD@)dv#`N+AS?l)%*S#0R z(-d4V;*q~q?juRM368_udyyS5{ro+TTqqVPQ?HZYvq{_2K%~8AHyr5;eJ>s_0A0Uq zQSFwS@CIP$*U#|z;Lczc(y|19!~N(B?q(k=sHd+>m1+e-V0!@|_PC%l7+#|&n%iL# zJ)j_RfKjjBm&Vrk=y`-^xF5r6;!LdWx^@LN(xkQ7V>UEO=QYREG~0FI#ttxwh}`BnfT`V!Gr50frE zZ4#t%x@2i|V3zTw#Q{99MW16rX1@-2r8dTRo!aX~J!=GmIQZV%iAw`k!Pyrw)B4n} zM=$4j!+&(+vvw5#%MJtpBk{xDyFRE+6hN&P90g#y8tLpu$Q3(cZJzvIHY36ecrL4t zG~q*gI7-{? z0VS#T);c^`(6%18GWk!P=|xn94XX#Ja*$w3h@tiBtjHk8-nib-aOH~$D{hye%?o1% z7P?~YI$fDYns=|PH51M$m)s=Tfvk*!W=lDY-!O4&lGs+g^tb&$e6F`~I>U#F<4z4U zJezS~A5rW=vei5Vs0gf_)_e4{#uR3sz~s6RF6JW!4rz5R5?p|T` zY)#8rX^53?b$C#S^RXZhOVXV&bwO+pnc6im*Mh=TmP8B}xQTGwi&C2l=Wct~Vo*RY z2c>)w={)>BR8cUTIVDq?LCFLbC&@$+M#WnYnmQ+#s0#iYC&z4@3? z6K70yBdtm!x$K=D>ItT=IFEPuV-7T9A6?(8O>eDA0)U~JV}{M+DcnedN2^sgqc;S2 zp2!Zc?_qq>?eImn56$vH@k?XZ4tN|6TKE9;9R&73zl7>gRDmN)&8Nj{A;wr`($83T zhtW=v(Kp6tk^4bXR)Qpov$^WAt~e-py=Mc7>q%THuf2RMVIYbGhiil1Lxb*=wHG`9 zsi@NwL0#|q2~Kw6Lt&hZ=w7?`JPs+AvbE2&1m*z)YY(AoRj{=a08O5EHNAWpt11PC zVa6b0X$Of?iK;tf6Ix^R>JezeqX8;|DY$tnu?gx;3zY;kc?YwB>!N)x{=@q;IxVXe zW$x{2UR0o`vTEm==1ga3JGj}-qG`w}%9|aPs0#GAa*E12yn9V>8V`7J?@77kwCMRP zlPSR~O)eqg=6bM^Q6vZ6RCuW8>d%-R+v6w;CAUrAzLoBrip>ocFEUZs=&XxUNi`3C zVMQYil1at&1jo*GBfZP63T6WDDsZFl#i#{cw0X2JYW6ZW?F*KXK<88jEk6ZNB%yhn zMB1uvp1;a=4;c-A?Ij(kKuw`HTO~o{s)po7Mw2g}r@sU9g;&ybPAsx^mE2wr@^x+i zaTS0xdWzVb3sZY&D&ur!`2<3%5u0MNQOgprB%>Svs-zH38c0`Wsg=ORF$t}oVRvL? zNe42By61(1qC2i1H+_QcmtdXsQE+o8oMd_G1*O9xQypw*zRsBB_Jfe> z;HTyZEN)wk^s-K5P$AoZyX$9H=340TaMc@JQdDkL@mgBPce zL*_Y%JaL(xYm3sRrmJKGdb9>M*jI)F&zHsx4inUp7u@nL+mKc5Dmv3dJfo84{$sPg(u!$*^i#bzE#?JUB_1a%Eb;!hLZ@>DPBtLvqV!*RC&d8%5sC; zVpfL^+{geLeBR1kstm?7Sq+?BY%y>B^^D=>o3hbxX!8Te(IG#D(vpbe$CHapj@UYR zRXd%;xK9j$9##+RV@{3H9ZTDNFH3e|Ou7KF-?qBtFdQD&DVUMDVh}{eu7DRz6|U{; zyfUuN_k>dO_49ebTns(Ea9#?uY-U#pO`TcR1x{vy`J{nXFXq6|8UX3B_!ECkTNb?T z=t+)U>VvRwfq3aVsk6tuNyvur_C?}5^;K&jqvM(xe-kwVnS{@%h3`S2X;|=3osL%n z8`I`Mn?u>wDsRs17(aR_d@Ijf&^;%O1NU~yQsX_C6tDsa&_)t-C@$hrX&}|G*Ien3 zXy(A=5ROSX9yO%gX<;S4^9qu;G>(_Ja&Nf?O!i^&$)UUYfB?`d(XOl9*KG&VNLp^M z`&f}%-~ha-!w?8c;+mL553 znkRO&W-^xG?6gbpK2y@c0pwxst%)T}tqPqYLyKqW4e$B{@${_P?k$wQ?0nu$FCgur zJlxf@qhA!7-jS@Cy{C-Jar&rM$8n3vL`l#QQIF|4kiNQ#+-=K4$|GO(*V$m6m=wyT zkz)=gO`=81+ia&10>f`{c#3_IcZ#C4;RNLD&fhs)d+kdiy(7A+XfG?A#|*8{1%{;h zX@Ql)Fieg)n2L-dRh0UN{JQ?uD^Jooj9n2fVr=2c+A zt9y+t>QlJTu1)5;4%X_-Y^O$WB6qABmXCD!73MpXD2Cm&oud}p^?P?%ilbT85StUZ z_XORq685&7=G>Y=>J;<}=nk~-#ml$FDzO#&q|RIm_0~|U-({&q()wmAvR%C8;CKsx z7jIr8j<1z*Ko~Y8CiqZ39e^%-(f+_+G)YPhT*>aCzcrwEW^OD8R_|!S%eAL>+l8i> zu=~|>;_+jqJI#76GI4HHhQmUfon{;qyGbRlqo1D#j6%u{2)r1%_cDrsuR1+lX|!vh zAm-lm5I%1g1%7Y_z0dNt`-V%3#rlOY3di^hgkXJ_B{|E_nMPny3LSNP~D zTV|_I%$v?%&Q=k*XBA?p~u(;6keWJN6A z>rC>TL@Ga`sM3&iAVW<;9>Ts!2&~68nNa8x``$)QxCkC`kexNyY^PCG;F&P6(z?D( z6!&s+f@348W!RTB^jz0B#*(`dGRSg;l#~|*U(SPbXzW{wehiC6{`TbEnv!bf9twDy zoe}k5nYi3G+S9{)lGn`=d-B9e#C)<&-}?ewLB0UD0`!LW9!%Yi&5Exe94Ul}zwK+Lq(|=^p8|2wBh6VcEM!&pP?dCN>*!OidG<~p zzfnb(-NYclj>!I;!yBE20Tt2x@Nl+~^U0>1vax*n1lD=J=$)6*dj_Xg4_l?Z@iBB zX8KVh4p*2<&xwU??P9uLaAd@DpxhO!+LyHus4a8mExZMmC`fGqJ9~aZcp-zfEpLR- zycx=qo6_I1I4cnV2=w-|c+NUwmcF22Qz4u^ROv#JZ-B8i2bib>IWr2erEwv$`@BN% zCaL!>n~%Ll__lcM4FIi==tM){kiXQkmU@utNa6Nc><%}41c-o|-h%t2f-&k_sFi%} zWb9i;9P(0QPz$d*O!KiwJjC1GQ^GQAxsZB!1*qs+{o29^;1HTj-xBkga6=K~qg{Rb z7T>mm6_g~BUmToO9#V9VQ(;{9qcgfh5Vfx5$M|Yiq7ruF##uF%i9pz2SLu*}3DOhs zp`!=X#jm+jOCmh_RRFt6z+22GeiN%63Aj6HSNsH@_v&i{dd$KR`8=!xuaeK3wgcbM z&2k8h!41FKn=}cCWH}wL+1<9;$qDw5jkfd<<5!Ns9vY8ZXIw~_tTgBmH<&t~Ig?{% zK=T#nQ_1$+Gn8qDX#>w8+sqOw@d)I1GD0gTbioho+}k_6$_jNMf}G{KCcV4vxF+D2 zoLo}eWR$ZS%V`h2U!*s*(J;CL6P0NK?l4m`<$CnMS&`58%55cD zk*l71ae>k;;bDVTF*iS?80&UC%stC#(SmiSm(FmjqJ8=XLe+s9@eOfzscw>AXv28Z zMQf5!G2efP>u9dVdyj(j__fY+e72|@RHJ-lS`?&p>M?WiCTLfXj$B^$VrfbeDQX8o zLOywsBYmeK-ja`P4+sy8T!Ec8-*fh;(_?OBeZ4q{gE&jCX5iT)u%O3p)%_J*TluBf zkv%m9c;w|#vza9)?0 z5iRm%?^eGo7HjB-iI)j;WEv9=xsrV#jE`TGEft}gg>Y?;`Y|m`y+GJCNQYvAY zao_MZWTBzg1Y}galq^%VEr>~ORm|q2hf*tD5Rxq_2eUO8CfkRK=x6VVNgE(|`djx< z5F?8zLNs62ZJ9en@wdSxk$$=ByViUM${~XT9l(IZU00#cIsmm9E+#>ku_F`#Pt;@R z^_t1hSdg~^@7j*3mKh$o9g;){y@KMA#z=qAZD6Y810znCi-gaNo@NiyV=ScCqB8iZ zhUwmmnj4KFBJ2+0XOciX;)xJP8ci(|guJYRm~Z8P8~yp}vx___mkj4K*+Ej>eVvhF zVbACRhC;_XesTma`viy^R>&0F6q*^flJdRBdCr`Xudss+YWv+h_bKB%=dy^!$_A8l z%Z+D;>JTe)?ORTyv-&z-cMq8J4%C*tdP-?Ex`*Tw)t1A;1(KWV{cK*6ECcBX+xEl( z3sVFp#$cfd<>@El1Yd~53$XS_@yhLGo#?ZTN065(DaIF zp|IWwQp;gEiNij~Al5?KrUwD3$&62vs>sK4TAudaGT(FH^Im#kkMObM0j<5{l5@$2 zhpj}u(3BGo`SID=BO|%q^udgC#Rr$ea0yjI3arf~n1)YB>_vDD66)Epo1L=DyXZ6D zT2=avOISUI(yRO*twvfp>9}GV@+pKa~s&)xoRG zf&UmUmJRcoL0j{FxMM*;$=<4R&PJE)bU5369?DiQbeFHsA8Bvn(7Zf-W-4K2!s`U4 zX6OAQr-i0W#qlXHM%D3~m_6hHg`&w;UNp{T({;>T;F=OBdjrpHXRoQ4<8~iDo1G5m zphC+>wuZ?oSy~iN&@EEd??`2swT(b|H?B)7han2F0&QobQU^7pAglTw=SyNNK@i|K z2+btS7ctG6tOo8EK;czUg9_J9N`SAn4-63}ce5X8bsyI9rCrhF6qJ7} zTjBlKE+db0lLw&lCPB9RKt)QuC?<7;J;MT}6Gz@rJ_;m1gH<~T3&f!$VzN%Z24>YIX4t(b;o6cS(gg-{-ydnmFtgy%LOLs{oy*i9H;8bsbWo z=4|=RxPLLO?i*48Dp^hFF=tU;$-;v5Ue74`J~vpMz0Q15(2=5)BjuFIV6`sFw)J|a zYe9WpxuoTulNFGcC(y&P$Yul0FxJgD-Gs~saJ==LwOYjxD~Q5`0XmfT_DSHfz1P%R z3r@75)`KR@1=Qv(NlZ%37mJE1XeD7x`sz6^u%DHTb{ry{KQyI&X%hLWce=XUjs_lS zRRDKmSmm>RmKleT7*IHf<`G<(ao1sqrb=k-~}Ox8i+ej7+=$YD#eFAJep_ANu!yD%V`ZNUz zxpCi7T1!1bnlXGG1t~b<_M|K^+PE=`k9yx>!li|Dx&S61A-s3w*rQ{qLXwTciI^Gh z+@JF!TJx+KHkW{R#=9Uo?|2bZ^q9+~83-ZxezI7AL}6)>J)Hr}Val$vM+(+U9I$xokf3tIDwo7UXnSz;2ICC;2? zuA4B32Q(?~nL8$)y6@smxB*=`WBa(iuE&!K9g$ru(=P^&>9Use_DW@HBm_{BWLvdl zWP2}0>ScI(&4MkHtQFPNzS><&(Un^d@nJ?agn)YddQpafI=XP)F7NY!dOc}3NPWP5 zUO~%xuf`V5B=zK;?yAMvYrzy2W|(WR^R|AdM&OB9V-LKDjuCZrWFk&Q6esLeyFUGX^l0>nEJ7=lVjHYqG{^WV?g7pgM5VwK+*6PJ$$KJ#*v1qJdtS zP+0{phrAM<=R1~ad>Mn#a~CXaIL}Kgx1LJhZ684HfH?=74yM$b=vG%sxu<%7kGOp~ z3_IwUAcePy$0?}gM3`-#3rWhF!aS*#w+*`KV?k;_U=`EE2<^cd&JR}Sbb_Knq%2!s>B{ zoUJpi5<)Hok9P^6q_!hiY618Blw7ra0?kv(%&wOvsYGpFzI}}^OOLK_H=tgug+HVX zvq~Kl9w~3wq)!gZ(|H%4WSHr!Ohe%w+<({ zJ&AGNge=`a-ij3m-l629%`HBW9GP=A+Fv{PCQ6Y31KI=LM_N00JW-7_K^oA+sH_er1;bf?R&Dr zR*&P<-w?n=K94&!6#~O7nFX){e-h1D*`RMGFV(T_o?12E5m&WvBZ$|KixY^tx5rvD zMr^ZB$4M1J^|q!Cv1O4X^2!R2Qp~!L9HMujq+S74KEg^dg}zHxGj;+LUx1|ahVqjp zn5rdc6k92!ki^#!O>=M4=mOzokKS$pZcl^BQ6Q$6RU?oT<}JeU%yZ$IEjK6T$k2NU zYp6+R?UDP|)7*p1UCybRQv7w1g|?|r*@JCzTjRXaB9Ty#c(u&n*qv~Lk`n5+; zw4715ogt35JH5+N*2u?zeR=MLpp9|__M&v(3K=&A$2+5f5DBPRwXVV+VXEL53ps-{ z=^Ctgl+obIZ-;?pID)wx0Drp%KYO<83o{3>8v zw9r}^ePEEbwU;5&xICXqXYhd}F9UL7ykwd2JTcCfx#FYPZ+E9fOKap}wH9pT^^3qz z+gIb{vrCxLk`~R?wG}8=!Om3c8Ey&hU2{eN11eMRltwpj!d|W3xeM~a6g+VpWU2QE zU_n{130pYdHS7@_siX+|#Yr?dvTeOP9g}rO8bN+tEC@y7G)x}Z4eyh3kuS=-D2e4n zm$paU*h5nkhdq)lGAp8Q4@NugI&p1{?-`f&uMs8B8M%GEVQ-7GLjn5qCin%`^H_VE zjdTO-ODeD17p`MU1CKdzCE&G52GL9sZ->)kgkmrVSCy1}exN``Q(7uosIhp5(`j3e zrTh^KmkYJN&FhPJ4Je^a(mcTb_i|;K4h3v0^b0dW@}(c( zZ@l`Qy~iP1-f7Bp!50Ouu5w%sL%VPDSnoF8B0><-OrAc#Iy%RERElYKjT)dVPK=L& zUNvvBP=?J#L7#?_1vp6abCk;A6m*GsnA!V~LC`rx-Wx_Xm41v}kdepH*`B^q=FWMq z46xoqO)Wy98gDnRFENGj36aE2C+m+*jOIGNr*G zbcQ06FEX2tdz3GrUKvYg*^3=|EZEM;7qkA169$}HHf4*;8$H6_T0Fq#y=2KpBRrT! zD?>Jh#zy+4P&7Su_sW|+Gsfb;7%HOVE-yGVEq9`g_j$ha-aK)a6*Jz~u^jjKJ>b*9 zeq6LJcBy&5O5X2qke_#Czg{4Xpy#4`X^Zu0=R|pX%ppC}`@O42FU>t$AE$1YiW2(i zOcsO8)pH{ZQ51WUN=FAyV#&a=x57i$+AK(>S#Ii32*!DuAEfi zgks(kxu^=_L~a~A)VQz;7~qG6tm$XMiqqaP*O12!;lt_bJd+;J5x7Vtv@VMZMOMb? zJ!!C(?yUva)C!bo+}pki^4fS!yf)eWVvibhw84+McssT48QCFc z=ur?ywx8&mUROu0bUuhEoZc77)V3hxP~Lm%+l0KtPgQw{Q05bJr7z3}tv2U?vT$VD z&b_=vRa$Y~Y|a_VqrvvMrJg^1>jA+Cd5Y}sEv5`9kx^u8!i`+B5`vrXb+%^$YlY$)aMd$8K3q|xV9D?4@`9`u z&sML&O9>knAA6UtB#@~emOsBy)wkf3=-Y``lOZGZALlZ#s`BV^C*Q_F(6*!+uCp!~ z25Y6R4We$0v;g5F-RF$0^$vlK1|_5b<8Gr)mDGt^;<@v|DuxskgvZomJ4@H~%`j5P zo^X;t!9%>czJ|=UwAFLRqFhAcdundR0;i|MlGc!aX4I|_mmyCQ0cTQlt%?% z8ZM*h6?IstCTJ_$kaOsJ&Q-zf#|wnXFb0Yp&M_^X4;twiqDVM#fgQ| z^@YPMBfZrudV2bJnB^Xy8!&=xvnm6cf$!0btwoX4LOn#TvwQ}Xw$Y0%ezE8VNPS_6 zBQ7yd=d{WhmHpj3-&j=MXXlk~+uB{i5l-Vus-ggd(FvjW9a4mnx!H+;#sa=2+OVz_ zbc+~3Y1;(sH~{uSpa^%F+GdQX0?p}=4b?RsgW6JGWUpkc7_+jbC|2^0Xn zFfh{fBLr!-=xyloVVBqB0}2kbTgt6@;PYS`C|&@@16XvbhxE*F&Mh2Xqa8zRfge9> zZH5B$I4mh9A+7}j0czZi!hLxpOqbUXD|`2*?Nn088eT}BfMg6E#&{`50 z-#tiP64PoGzJiyl=wUi^o+G15cGbxOgOAv`c|EzjbKLnghNp^)M5Jrt%7fq^D=@N(W#? z#hwxifu@I(&{1e3gZDn#05j?<D0L*BfFrnWtq*lmppEbGQ+|)^fc8RKpEVz$!ht}VPKYrcy%F^a?1x?6=r0LtT0mD_Q@OeyZx4LwzQMZ?%({F}4z*W&26JiWKF+ zd5t3LWq#?KiB_)6B3T`vyB$!hWsWzVl1gF_5w&Y19xe7zaZ?-SweL|yXxxYlJ{)Dp zk<@1i{j5+P(hEYzS7`4_7!>9mohqI0dpnATSpWe~BeEQ>RjgfOY8vFIlb1c822T&s zw#Pnh2@+K5H&{xJ7J(|sOp0L}Zy!%mkm4Csn?Epj6(ay@BvX6~FhG-*#08xp2wQ>H z7`OdwHZtIey!2^2XOP`0TlN79al&0-H%+ab1-1pud6LEP@@mi8sCAkUQ(pD-%yx+v zzB*;LJ7=7Fd(rz$xX4vOMnchWWZk6fcI^%9<&e8KNO}YvV60M_G2J8OxG|-N>!|$l zYGdCEZBx&L=At&)VyPKeJ&aAlSGFBWa6KzxO$4tacSF)L@8s|}GBD^(-Za1JbbJgf zsdA&Nb-;uaq4AUijsZx-CUFq4;o^1Ba_EFD5>%+433ti1Qrl3Y_*z0rJSJ9C1;|2t zLfDef2MMavUNYqG%&zb4WU?KyRz=^N&2%d(r@Nv2y=2tUe>(i)Me4P zYO{6E*QH)UK_{%YjdBe%O)g{61875XW;x+-tmPu)!g-14qtS1zOoG$x+$_;p8_6~L zDsu3NLB3a_M5&GL+&q@uZfj^{_O=COBM~IigFrc{a2}y)x@u*QU!MQ+HWT&GPF{ke zH$;_H9#N(OH&{t08J_BUhT$=kSL0rm_B;p4^UUoL!W%fee%#vm1Vn@46;n`=;|5o6 zJ;R!PMA)3F4}5joMIQA`XS!@3@gwer2xQeZa`+E*S0Y{Zm=hIscg*MXo~sWR(zYtS z+Jyl=!M#cndwAF+y2o7Yy?xwQZ_|m4_x$u^sbPS5aL#gA54UrRR^W(s;-m)#E3WWP z^O|=IRN~OtmPvEW1Xn~PkTY_#j}7eQTid)`IURiLCna=>u6fLIyGzb~xoHFpOvkOw zL*&K{^qdFPXpsu~@f<^VZxeX6tJb+>G%*5UjvijsDkkAlm07i_8j=Dy(Fsqfh762> zGSwqnPGM-WiPh>JedpYRy_}kiGu?!9~+#r1-aFWO${U}syMTeDmdz05txQfzT(QG|}e6XRY+>_Vw82L!Gax|e`whb&S^ zdLWYME*q}F8sOfk4-P-$IDX2t?XXp{H%sdHgbkh<@wKU--YO0jkOP|yQme|iJ>lc# zGJUiHz=mn?$dFM4D!iv6k5kg>bEw`qX^16RAC$vqmKiUd%HdsM&Y22B z)#>@mgM4>n`n=<1DWG^+p*}=LyxlNLuV^03$}#mPG5DOTRuxxD;8fy>I8fp zg2Kgf_>NTPax@0|3xax)Re7$~%bwRyE#Et;8*NVDXmD|; zY{xOd^9t1>Dzg&RYA}8?5{ZP~vflZ)rp(J-VhF_>2>WTB%Y$JF9=U>I+$rAiq-;nOzRwCIv-eT!v*KL zR@CjLfPy4X6$iAX;3MF8VgB;9Tij>ZhBQvDY+)^_~N}S zxCT(RMg`E7vx%dy$O(Idb^Fp%oFw_7#vXXVwLzOJ`i=CetN?Qh=B#xQde58OeP%@U z5EBgo&8rbLN6rXi^Qt9iS!-Iv*3E>upNJ`#1%<2+I=v(XI9>)1ZWJe&CRw)P62%SC zsGH6;Lh}bke_j<5?JC&k$}lEL^i?}8mR23>@aE`%TTPWuP56UeE6tf z-Io9iV2oHL#GvS2!L?&mL`W5o;N0`WPbTD;7J&Ozv8s4c7E=3 z?)3Y~qO3u|9*I=c&(Yt~CcHf_-#vQMt^m}W%X*j!wQGqjUilWE9y6LfD0<;6k1{O{ zcL&m(lVg{O!NCYNMKbP*Am(as1TZN1MV3UrIQkEEW`W|DJ3|L;b|lVO(`Fs7L_w9F z660aN3{vw~A=ZJcRZlaE260V{;c;Sm@4%*W3O%-5*TISy9NLY{E4zx5eOcMXQ5|Qa z;m#R@1#@mq8XS_KE@Uq2Df;*@xwpi&S7Yuy(nu&EEoW@%hRG!jRcyDZHw!H+*VU{{ z?g#`Jz!l^`Jv!MJX(u^dpxe4&6v>wnHAkskla%M!H7M*(n2aIdV{bJ=S*R|mWYV>! zPq;N{bD~Qw$y8{wL1g-Y>$}6kzEB`56O#%{vExMc>mn;9T}z02xC6TSa!!&j%Mg1U zEEdpi=T#gXO0y%7A&r>>?}4in^|R;e4{~3v^$PzOd@Iiyas=MNU~VpwsS=)Wj&&a% zEws}rhh|+1E_h}MTRIpxFNt-wC@FHCZ*;7@S^I@37hNXY2Zpc%cr|i?o`DyJ-z|F&-A4R3V&8axPo|#bN}r8?%~=m$(4--uO*Cv_9cY z+?uLu3YRM^dLtrvTr_~`CP>Ky0V&F^8A!(={P@5S^4@TRjjPG!We%5nKftiW**TJz zrKqdd?Gljgaq(o4#%J_q=6qIF0ebQb?x-H@8?q8d_Gluen_c1Dh$2D{zIr`n?hPhx z`Y<6XGD?RgTOlgZY@qdYe6!h@Nr=}iU-&9kn~T#t<~N*GPp9K< z-Qb~Q0p0R2sZ*JCdDNZ}`#1(C(92`!Zv&`$9=+_pS>rza?uGLU`9$Im@b0P^m zQi9M;oWI~1tsIM`uFtq)lk1DnnK~_+hcCBW9wRKCrC2r(S-y)B3D-PR8>-hKVCxy{ zCvUoB&&s*;r_f#*qSy7^Tantc;I}o?9_Z#A_1=H9Uq`ElK!7ja^UQ#CcYo2b7r;HD zFcq>|yD*-8ma?LpOlb&h1Tk@%BHLMGEF(G~rxRk;!|qEVJODYK)?!W*t|vM#ENj#C z1YSbxKXF)w&Sl^j=0Y$bfP8r&dDH)`3Mm#xGcYXmb zx6D_vCSU`;=%HcN-kN%bNjc26920Ve?vt9ha?~9fyg>!yu}EFAc5@!BO|s)WW~bR! z1!>9cUZ~itNIXl&LxVm(40MG+5odm;9h#c~nQVtWloUE)?Sj;*3(a zCy__S55f068C}7#V!z1;a7idyK&*>X30&<{$KSO37wwm7b&sLtoJ0L7dNe}I6)76% z)0ckR&3N(nEjKA}0U7w(V_esH$xhbzxB|J(vWMzrxng)Z z`YWm)Q5juk^v*7(8zaMJ%zbCAvS;~{QaX;e%dwYLb>tvN%x0t!JOeml9)MuPYc5h2 z%`oNTj!2kRv!+*@Rd@@c35jsbidOU9zVX)*0DRbukIQf*D_2o*G#_!(nm#P32OQTo`KVFi zi73+OCBc;UW6KBE06~a1wJ?uFTbpdgpZ6+FK7-7F5lvOVy?kQtm1{n>A~*17MJyzP z-SW%;1U4{ccJb`jg$ba<(Z*?udQiFP0i_nmMD19FFw>dbnbrLsta)KoYJoS;vuE$2 zfd@&t^38itlsn@iRJgCxxR?v;AUvj(z#@ z4!#$jisfLY4P?_)mAsoJ+!a7_X9rUJu;B@DFAWqer2`ZpPuya#0N=VfI;h3g0FVZT zM!ET0h;!^HDxZD!Xz=m**1!wN9)?IViXF&&i&E;wim0jHo1?Yh!w9;xrb01z&xvb% zvLe7`ag3|l`0et`5u>;I7Il`jQ|ZbD-ZQA7+(CO~o2CW*sG^ixJ<$djlIZmW9mb(l zYK~O_q`s#s34!xmB!^~2x?300={XaMKAjQX}#;$y(kZ8(JP0fp!)zTvwId^8xG2lt#v z=?0m-ABl{>!V0kwJuRXEY-whX!tlcC%%NT1>DFo_XlZwl)wm5}6n#AivYkDs8vWa)8IV#Ov+EMju9-dn8U zeQ?*P$+)jU!wF;F0SFs~459~?lfSB_uI% z_2;lCoVqamMocia@U7^gKSH-T2Srv+%X`3$_9}?kkao;Bl$AtgBGQnrgpb~9XIA%n zNjXQGgPM~xw)48vvY*=%tS8F5W1#)cH#vPN(?`vH9_?J1G`I>0(X4um08OGV!oKaQ zL8e5wWv6_bkRctb{f@C;%>nv}RG9^Dn-rNX<{lnoz-^i0YHX#SDKsTfSuQa1u=~1LrNbjcdq!=pSFXfQXaZ8`bp)QnloKyYScIqN++jiMZqHQZGgLX-$*n(!4 zr8q~AT>WSa_&sQW@OLI6G~zE$qDPw}u-DZ$DGese(ll8FB}{`k9%qy21CM15G^p3J z(?UnF2pJfJ&-JVxk?caT1PAx@ouOhW6u&nHp&CIv5H3Aw0N@lAXj$g*1LZ6UBZ`~o z-n1$}qL!1o5GC^pBB}ShJN4DfC_R4qP>?yaoVmNz2$o+#njlh`kIc)yOHQ)|k!D+BHa<*< z-4)-mhkTX2NwH&`2U8(CR7KPcSRMn)fsNJfBDG$JpducmImC}gY*_GJ5P;bFN?t-E zrh~qc6uOko+t#4-rt`!7rP7ct+2@*vZ=U(yV|)(8Li=nV48s$>%4?_rczfqhx|p8f zJb-pzdQ&^4uj_Wx+!qea0bO1rh;^O3rAFF@6hIqYC-y*_bbqs1PI_T_!PYPGF`Af` zcv5+dxv+LVXTm+HtSz~m+f|dP*z?k(y=Y>qq25I|Hg2-& z#cg*Pv1lzvzCn20m9MqMj?j3jaU%@__KaW*l*?V+_Hm)FqY+p>I*PX!V(rC1Y0K9^ z$@?-g{ViFen2};HRt ziG{}Vp^Y3Rg}bvJl)!tH4N>o9oe)V})fmr~21ep_Sjv)3#XJut$WP#zzJM2r*t^S? z>P@95Z*Oqr2@SeaAy7#wLuAZd9|DfFq{%hJ{F6;R#zX_H2%XZP`jXuzGesH+ciX{}DgFr?KL< z@d}hG0e|P|3yTf&&zFMOPrE}G{?rQ8`Q z_u;$*(13gzE9IjkNYW<`)!XK1*Snq7vB<1;5jjbW(YyuSv0SAfJJa`uokI~Tp#&)( zHK(`4y)a2R+MQR9@`zIWEpOVgyq+d?g#ctInO4^3%kTx)LwJ|ML{w;RYmTbMG5`jVeN>Yzd zfFbVdcQOi9Lq#zd4K#{@K3Z$*YIWn&+nYt3aZabT%Bydr0J8a^uOC%4opN)ZA2VS3rnT?o0L7!DhdOCjM5_Dz0m54W-w4X_{4~5fiYOfg%B!E{vX$HSskfRcbsm zQ9UMOOrF4Px|i-WH?W1=QmzF_yd>|H^1`hyJ~>b)^RpzWXXO7V4#*cY% zl0=0m3pix6L3f;lyhYGwHUV06^(8)xenSJ2&+&W!{q<{R#Fu=h^0-DD2~nP1$(Bj( ztY$1t-7bJV+?UaPd(TJ~&)n<1u70W&TDHxB;~O(jrhO-i*F>jg2c_Y4Z^!nNc!4kC zce~1OE@jdPAKZXFdS~&*@tn^9?+jO-Bj|9~Q=p^vsN?lh+ZnMKkZ^d(?oRo3uiEuG zG895->sIgLzeH~=}l!mV(3+QftOdzsM-$Y~qZVsvHJK9cB@gaWu&!K}80 zbo@xmrqEoD7$Czd5;0bX-rWPqyE61edf@xs+Ul_&M`{Wp;|-26ThpWTYkuHxAlMIC zuy!p=6}lt|(q_ui7bxPWP^uq8tCSZuw)iUvsp-DqR94n3I%3p-I%)x<8_H3?081K} z|Bw$CeUOgWWu;8W2yKJ+bp9WkSlL`qYneJ(UaL#j~tU zYeO5?+zPKoI0}3(Sp(`egim=$k(hXOUyRAcA!CfZu}YVIoBTMa29-!AqzY{!78dt` z(N*)#yml1yakTZPR~_?&g(Su!4S=fNd&~Cj1z}Xb%TD0M3UOz6vl)I2Lh4uD%B`N_ zoOa#MCnkz~BBuC3EK}od;4OpRqXNNTZtq+>^ERR|_0hoIC`5p5RN;#2+9*DOuEseJ-O5NYiaUVPb^46-+iYX>Uewf3zNI0@lCqfh_i`%CO z7YAD@qrLUWpJx#@A8ctFDHCU#-2=mubi8b5k%tSdp~{Eg^7NJhMtChjHJ5kAuIvfX zIZB$h)be`Af6P!l>Mnyv$MM?u41-Tut3st}R1cX%xX6S;exqnZ#gORUC*a z`fd*Odc)vt^?~wRxyYmkJVcaKdTT)E@2ph1Uxv_gc!{uTwOSO_JQH|3z(hxZqk}&YxR^?7*vCiwRVIOhY6pvS^s z9$kyUQ_f0YlB0P&j~|^NkEFyt!_HYb4a7me*AM9odoi`+;O$U>&}h4rOvPKQmzDaK zj;6x9OF%Hub_o|edkI;P+QzVlTc}FMCu?45hUl##9j!@oq2rsVRu>psw0o;d*=*`s z=Lj!n2AV9&6=7}>iD(vUk-M8xD_z;#Um_;tW8QL{;72jyPaIA4l(9}S*GK|g03qHs zgyOwxW$hYQ3tU*(-E2v9@iff!u`whZzUoRA&3dXW>ms=GT=`C{GsPj`k>|_ikg#R| zs?3P#fCGuWm2c+NzL+2CqIt}~e6fKX%192*&fRj*8Bf;Vwz&X|(uNgZTDWAZPvk(b z&#Y!V2Jmt8mN0k|vqUcY4E?Y?cbp$xjna;a>dO-%xNL~IbP)zbi>P?>gwKx(bB~qZ zR*6n`Eb?3lyO8fLR$eSRm3;UZIPu#jPG50C=(Sykwe9h`%-ryMs=KF-VyN@m3<7TlhxJ<4{Xn+8A->*%pNmRTLAZ>*;Tyj z;?0g>7{f(QoXx5rnzU7WI_>eSM}%zWUcP+sX2*kVA$cF+thP+3t&_i&0v#lTs$uJe zWvh3Tj3IiRR@vB@#Vt0{PNK`J9#bZ6%hUD7o(ON!VG4?c&V7@IF7SdL96)@mw_?0#0^p>*{GQ9E=q{E)(>8TFa0w zC~(>05iMIj)8{YYo*&T?KL=azhdmkbY!ddZP)p9^ONrr&CVe~DZOtAIRz=NJc<+hK zbShUWD)Bp1_tVSQ#>NI^F4QT`!=)5#Op*Q)0{wTsj)sSZdr5h3Fw3i_xwCV*d85AtP)WQj& z+W>r+l1*ne66Eu8_`Q<>Rp~luB_6`F)#DdqCk5m^T~YYdhl|6EN$8HdY-cUm(hhWr zs|6Rv4U1jMW?Q`q!Uj@^Zd7OvpDxy-KD<)OH9H!&q%$W;@5}7UmV6SF1H!ypsG^eR z$4JFRsqa07<-Hnyu%6!8s7~q_53iCdr3U8NKJ>{x7v^D3jUj*M-9f~%FX=mp)S)vzbo~f_X4)$|nydf@W-lErhx$WlqAN9d;l2W?-%AShCb1GO@l^RQO!fglb zg?A&T$<^#=Nep_#%6ECF?!3<+c{agh4%NBubjLa4rO{jrKFM?l^?fEG23O(^b4fV! z8sPYGW*i`F4bHyB%*UsAE^}-(G`&{*<|QIhTU+eaW9eSXLQ;H}%YJaIK`9nYid}Mo zd2gNIaP5StE6%-~u1{YSrQX0sYH^dRP}XEdo8lfOXtFCPno}{claV~l-BR|Da(c4I zuZZ}}lBr>MPW1RNP*AR6-JdUyx+|6R)HvA?TRuSOlxSfHeQrH#58=Vi@()cpmbuH` zU2+7W&EuisSmh>7!{dUYd9lKv6^XCqJ%3N$aCeHWd+iG`skuR`64`#^MhXn?4X-S> zY9%IKPZGXSK5e38J$Jqu*yonVPS1)3AM-iYZnYr#h+8uy?@KXQl2ELxeFmzwY~kpj zB~Z?<6y>scosfgyb^*FbFh&mFV#78@DbV@?M{xM@c|S40Gt8)DAUssw5zUBdRLFF< zItVC0mPyj0C$Oui*0;4_1}JFa-|YLXuVBR}QKzPF6gb9)UYNAM``VQ46DM1sd@F#450@o&|lnH!5ZtZCT z_5kdq%L2Y2%Ll?fg;i`gd%KVE0ilqdJK^k+4erf**REWSuA&2~IUVHk!d9L8+1l-s zj%-qYnPQoK?jT3ErOwtZ#aUTdE)=QHdGU=##4(Z3mc|}K&&$bbw0Sf&1@Vj+ zoco|`9#B3)szuwTfftUM84(gQ=dnEj%$)C4%VM~vQ`WtHoyQdTANJhOn`nS|nebS?&V@%Hs9pQOGGie42J%^={Fn-MCu41k6xn1$AmfEb6N=U9x(j z!Lc|a&oO~aw>3v<@@8>UAV)#=3O0^qQ5Ti0&>tTu4s(;kMdv)M{$UxUdC@#I()Pk@ zIM_%5U+ZuIo7q)!?$Wbdv&f^3MaZQwaFTaCz5XnHlWW&@0kC=WT;6q1QbrHl%;tzT zUXY*^@goAz%=3H+s4;={-buoW%vME0*o*fH1Rfc4iMr3hSVa3pz!=+DdMW`Rx?;Fr z47*qZ0+cjjY&tp^CAWnDvPfUZNn}N6liP{lI0>FXK6Yax&+%OBeh!-vy>~E;o;VPC)$)ZntcAq%n-X9y(f!x;x_?@#ON$p;8^k%qeeGYJRVY=3>RLOgcLCBtf|=HcO+PsRon!_^n_1yU5h~Hxe%{9OjI70(XyXNr-aVoakf_vd+`-~)3-C^Yr310Z~f7fTSVf%zUYtf^fq4-#>8h*%>IQ`qAY zbW7K1gFqB$c;Ld%VDC~b&`(e@+KvXp_YE}0iR54muE&zHY3~VmK=>O{O+!dL zagBpT-@&u?>O;#1>nfA?s#m=kPqk!V!yJp_rZEN{aK6wc7C>wD7q0I}(`57D0kk=; z29ynjfimJ@O>N41MpajURENOB($rbXqr4bKmF5cdUX1s_r&IU%KARb0Yyg*YQ)V@|SztQT6T)P-G zB~`yCjWezmM$B2Ux2L_kPY;sC8rsrv!Yx18=ENmi$;gi?}58_;iI66@^vP^iz&v&Qzu|1T>og^8NPnICt z*u2cJ5}hP3C|ry8UFf^$VC{#hj1M@iycCg>O-`xb+-nv0h?ji#&>!Dq7p9$La~WeY z3s$prJ)v_1C~~0Ku@<>5!3{1ue3)QTaL7gHT=)W*VfKiU{k2l>y+a#Z4oBinAjWzo zA@jhyEhNd89E)WKjq3#?BTc5-Qv!#UFgrJPWHE=Xu$FVwYG3<(U zJRV)IQjGgyy7@gBcn574Z5INkJnoC`LA%zsdK-&rx$3MYfvV8TSZby*pwp~8ZuUE?UqO7U@B8L(uFo{0iZNEh2W=k3uQ(&x`=ba|h% zBls1UTbZh#SBc8uKD(?%KQ6OQ6!FFank=jyaoq>e=>VhjFq8~vqp(I@&oJ1=XY6?g zfWY&hr z1rQxEJyQ?-dNuKRYqRcBx#2_Mot73T8Adk4lgOG0?#&m2_!QkiCG6I+kAhm767N|F zaK@Y095H#WC6?o|Xe8G5#+>cxt664i3K$&$Tx(>vET$JP&#FjqqiFp0skA(Csz(dO zA)ECq(2(rLeDjUzR`6(?mb7LI+-oj=`;IN-D1@X*^1S_EPopQM4u^bU5$;8#-Sf`D z5v`cwd9y3|KqG4-(CMLs|3Dd$d8a0W8-|n+l?+jcE4u>_}##`GcSyvOW z97iA;sXa&EdrP9(%@$dun=wA2_;TNs0P|i|b=89#>&8_@f0>^2`spg7ZKc>Owe$0c zry%LX0c!=WDcX9uO4p3C{%rE)y8t5Vo#B=ZSNC^lq{uas&BW{a_#KDJwE7@zxK*JG z3~YfI0yQ8zPEnwSV-e5x%8zI)!BQKBW=-Tt!nUC-sCuDzAPy34(}eWy9dk?Y8+t;o zUp+9|dpjE4kSr+wsGsv7<40!B$SeRw;#%Xr~e8;V9WMOVIs#K$Asjg4~KlfnU}TyTTErT2(%H zg%f=gvBR5$A?!|O@mSAdNRI^0-*K_@ypV!6RyIcf%WTbg+9y_RVxlkVv>s~q6YRlm zu%1p^AANCj=NwDM9ir~tL*|9UPgm!(uDxD*+&t>5i#ip6y**`gD+P%+Ar!{D(_{IB z?71BKxr4XDxxFE3giDz>J0+*Aa*JCiE*)p098aB`{ji_IGY@QCT#?Wy?I%k1jk79pfrATQLNSdIvrJaG1i5=nrH~c)pqnU_(^j^ z)GS&UP&Z-Ni!)7Bv+KubC*54enCMp`5|@^jXvtGlErhgp%F*`v(8U~_Oj*^VA0p1< zy6Y9jk`|DleZ~{?LJcyLeT$}1l91*RUxXyyn%bSrpr~T6>}w+|%2WeM1OP1Z=jrj1 zJi#HfnRF47Z0HK}Y2BDKDKJEpYKk0jRW^(&T4I~O;S$`VtGeh`8+jc6qTfLB3IjVx zm~~vZ91W%6FfAhsbIO|Bf{5ddHx?NYBx@u0L&asii(*qwaGPX~O*~L{JVDu)Xl+<1 z%H;2Yq{TObIYI#-qoZJJbiMYdgPfrIqRP79#5A*!q;y}%O?4ZnOM!NC<$#S~U(D$6 zaJ^gXJ!6?w^)eG$4XmcK$l6@PJ)NXiJ4b;-DF{|P8DdxZn0%dH`^YXfn`z#Q3+twU z3lbJ@1_GU@1y}`|8FS6}`et_PEk6_WXaHWy#F;Zl?(m#hQG6>f;C&7zMxfhqZ5zpp zWm2>`Y8d*Ommb30J%XCI>Vo1pv6vt}*w2y|6TA)(guMNR(vGH6ZuYhJX|$8A&5>Bo zzBlT`Gq}+3H1eF))?-H+n4TrSt;)yK7`87^OJ8NqBIiqbmYE`IOS~c~BSHofn=?;* zWZ$CeD;t_<2Fx==rw6W)-NFXkFOts2!u{cmvdiQN7uxO0_9BYys4RApZ|2d2Fc>=< zL|u`M8Ot01hA#|;0ubur^LdXc?kPx~!?e|-xA|TmgI=-UOkwZ6GqRM(wqjNvMo=EHqCt)4vcQth1< z#>FDv^z^VC&lb`oqvEaOF933Sy+{sPVB(?n@SyqG%hTi`;DOLu1{+DTPxO(#(0~0x zc^un^z;CeQnd69`#0&t7+N^;*Ef;vOCoZp(LwxiLyM=Z5ANbZ{hD$mrEYZwXHxzsoY}_cqz9e=Rw|dy{3-$hWl* zmFl$WWobS?Ww8u7ID zzM56{eMi{;vd^bG=i0TQ(Q~y~wUl9rj)W}RG2C5`#(-nwdiJ37RXxh*RL{b= z5cpom?TBkT_3ph}xD_*92qFQ?#q(iVz4z)G^1P(ou`cdeKTl%z@IiubtPMMRgRGBO z;O&r-Fa>$PBXziP0DvyVVEZoous zK#F|#Mjn!x?-;Wfr6sUsY*S0VmCPO)qq&&G+t8Sb?13vgzz%vV}f~M&!v$%$l0wb%M zO%6}9=niXyY8T_^VvLQUvt4&PnIa+4-&+^IqBOy0f|&2RX~uZ06GN1qL)FS=5qQDn zE7Bu8e6`s3S_L&huCvTvy^%^f)3+R>dyVhd5)+nZ#SotRb_;jygG!od=B$%u))1Ic z_P{HfUV<6m$o^!X;FJBdl#`p*NE{+&$6U>m@Oe^?l(tV}*<&S;q2K1Nk?pGqkGFRd z1o%$6XUz&kK#Y2JZdhp?mDf_*=OsAOJ;3hAuN0p*soKFqL;~0{dQ(jio%w?1QvFzi zl_D-VSur*W7|rG`Q3HYRl676*yVsCzZ6$V#JmpJ06Vq4eS~JtVkS~2v^&IaZjc?qV z7%C4BQ9EJ{$2o&A7ZLJ|7z2i2X;APDB=b@k=hTD}M>Qa$w%ASCbAMXgjtj~uaO&7s z+a!qsQfLno%ROztkbo!@{R%msbV9b~{s(^?1J8DE23rU~D|0U`8aaCCTl6z+(6pxl z(&xtSrt}??;!Tr_{4%{0uLlSPZJ)`)BMnEejfpox0RF(=^yzu_A;`;DqUdYRtJyY( zdsz9lUkdsmExXOtOG(|C@bJ!6857N73=XFgHlDNxhgbbF6EP%#tDbRSKA+sTa(9n8 z?JziaqorH7_*8+%3wv+39%ysD{ldsIWF>N@ZKnun7F&{sm|SzSwAvqu{}-hGFJD&bZe)<5Q&8jo-j9zP8sIv zoG!w(=Pz{y4@Ne0spPE?UEgZW+iKHfAeI-*u;Ed>)6N7t?dDfVGzZV+K>y zVK_|6_C0;;a`+yCHPvNnbqgo*v_PfjXyd*|tv(0GbO(pP;^x)|W^{B6BUmY3NVu3N z)(_T@ago8H$ICeDg7)bB(|ASF!-*%}0Ab9b;?-FLY7Ze%$H2cSLH zuTPSVJ9i4ROVyW0jSd;8c$}-;ZHHbx1LhZwjFSagLipHmGxJrVZaYDhHgYN2j0tRR zYtkFgo9l#QEly=56mzhW26|dOkeOTr_>RTm>6DsXlnnAa*+npsrld);+3i#oT4IyX z<~DLr^15N#fIR7mssKM0Kq{q@YPAvX_O#NdEIfJRN5{$9SZ$JNH;&~P~QOfOD*wrKAN zMG!AE=QX$BS>E;wUjQ7&Tq0pgmCEU-c<&x^NwNi#y;|-WPMldxU1LY#q|4PvO4eA6 z*C(rs6U>tn7x5k+n&9K*b@cFe7=uKa^+@wbBwiM$0Udj7&+_|AipFX6(cq;^z!4Uq)q+gZcFlo zkU5uIcG?xrd%V#>jM(z4RmH3~pbxOkaGNH`cVSqQt`GJw{awDH@F#CYrZS}i+EiJ* zyy-nWetwk)4`?sCq=b63$N+8p^s=#Dq{KN8B|03)dAOH)s1pguuvh{cQR|rPhzeOqRFv5q2U+62*vEU;A{3c% zERxgvCi015PP~B*Q?5WOplFGQYftSe-_ygurzmL61?sQqT0!{Ohsp@lL0aA zyu-{Bn&<~{gtZw`-uYPIs=UZ()>>Y@m{lUm+kJ%{Av;QzBcmpWY^Ovowd#|8wlDf= zT<8h&KpP%SSl>GI;RX|#CI+%;a~WnPk4-mloK;eeQd)X9!!^P^(6xFG6n_F;N?2n* z@TB(=Nf_ktS|AYjiQr)AzE`1`Ij;b3bP9{#8#FD@s`GFVS`IVlJTH22t3)xFKIPUB z5j&=NRQo{JS`y!|~QPl0MQt0%SAul~wpmmNS%r01LQqPO^&=O}k%Vy!^z437~ z2y`%5_lk;*PD>$pz+C>)k)}>nWH?sb2@uYytQSDf*YE6>n;op0>Ty#!=hXvYQKMjx zi*sZ3hqQ6I+{yp${Wf7kBmg#7>$^?vK{r(A?sqDzfJf)KT2@>39{&2|ZVy+A9b_!@ zG29Gvf!vKhE?5CX;JI8sK)EZ}WG7}`=5QSZegX^>2W>)3jLz9^<^gZ^1=bYQJw&>p z_DUVDV5Y~V2u_aBPf`0hKAx=Let9ikkk3ZNy`FW#>Ld|anMKlGsvRVOV7H_JAr+OJ z6ws3>LUc9faP#hl4Av>U$Dn(x1|-+dU#JC?Mb{?aHk4+1aZ5H0Ix0RZePA>#PiXei z92}33U9SBpbDTi9uIp?a=f<8xU*{9!rxIi+x4O?VY!ys^Z>dkKOE}k7*5a)j@mnXY zDKzl6#WXzCeUwLKB-tey1`E$|Uz=M&aeBf^ZQ%vT!nhOwus`<%>NTrOjXiW-vgaBO z$ZbI2tG%UA53pv)LXyP!-E#{@pqC9}l0+c@rO&yJB8y`#a(f55y<+S(m1cQ^2=r;m zah@&Bp=uJ0zdJtNo(AF9_tMkE6KbO!54KtKRi%!GI7&3%ksyMyN@SDFU7MCll}8ah ziC9)5yNShg-8!977FPEcms-8!Uv!?<^SZ zpetDshHIkl`RTow`NUu33x8dViRb>VGaK0761ANJH%szl4;ctP66W6&UMg9qBqh?r z?Q0#KSF71Dt`me1$_jIB8u3Pio3I^17LspoC=9DBBzJ%*FqaX!C!daIuT;PB*f+d7 zXbLr{W9H?hP}jZ&zUXo?oRL&HEq8kG78+GwbQi!#qkMsqM(u z6XSe2(!RkMh-zx-58rSpahl&bfRK7c1njU8gNvbqRPu7Z4H`eX%hjf_RayrJ|q$0TI=QJ=k*^S4hB<#xt~ev-L#>Q3#`|k(@F5 zBGtYvOJ{qb;0u9pW$T(Q5q@yBU13hfl3Q*OYx z4+{oBdp-QH@AY#!g!0}|e$Yp+p0qhpAe1Rr%`kbcrUaopPk~3}{o12MY(d}Iv{(?j zbv8#I_;T|>hv>T#nS5OCy~vojRMM8jIwK}4@4W(x;mFvfRDW;bt(!%`7=j`rPyw7T z<=ZBN7T}}h@Vz5i3{EqCHZb5>49K}Dqu{&lJHv)IshP6)Dm}uOx*iej%xEz|p1HNd z(R*(~E-6{aQ(o?40ZZ@OVj`SjB6)M0_xc`x52a53x5}tH_8=4gpMc6e31VK=2S*fpgpORRMQb zq%9DJJ+P~Cwk8+CyoD@!_8y_a4cL3x`%HDy2;~fi(mwfD`r^NR<5`v%dqA+iUM?S5 zMQA35RSQ_47KPx#p?HCi)#A8ny9q~sg_%3b1zQS6^B#?E-=PnkMGxi9y)OqkEBGm> ze5-s5U7;6F7IPiokf*Um^(bPnk*Rhaa*>{+zFw@f({wUci+YgRLk?TKisY{&BHVyS zp8@7&AYbG&ke&h+cR7Ip*QPB1ML@d0F(CtJazpegi5u>7MVM_eM(RPhEQ1u7 z&$!+@r``g47u8t}^-$HNZp`BZ9)%%1E_CyJAk0tRyV$gG3rj|LNpp+_Wa>}wMi$W8 zW$*zcD#;Xz} z&fu+3*psWtm3jn$Vmx;d-eH}D%Lv6t50LV5g$Yb6J25=gN+!`(g;$l^MH66bmT8Of zwUjThZBn6U@Cb1Ov#a6lYf=DW5jbSIK+G=d$C44ZWSywC<+*St8v~_sPh=j?@}sD9 z>$hGehVkG*Q9d6bSDY8^EW^wBP=R;e$h0pvg7vToptF@g?_+sMs4jr7B{&wklmOh( zJTs-EU^A717XU(g@}|wviH@w5cVCE1K^!&}nOed@Ja;8g6Qq&)&EVrzO^Zzl7@69W z^^%DY+KIEL2o1w0o1<354>!ONkbEJkP(osEBbUMRGP3VFF=oK2<^+8FD&tHN{q()a zaDiD;6-r2oa5Klvr89Csxm}pa~>$`JqFi zv2IajWiU^r)KRboSfUqB@Ekja+sntZJCl@WN#>TNMI(pL2b#LGGzXfP4695hlO8VV z>0OZAh?fMo_<3*%*F}WQc^L&oVB`_MQ3*Q2Gi`c;S>*Wk*#R77E;I;9de=p3#yr@k zs~Dpmv7Mf*@^tKhB;7j-!&X?P*URL`(64I`?&a7MD)bzorW!v4|33ccx9~-lOMG0$ zFA}fcBYb2T9F_I}s~i2jp=WOvKuY~lIEMCkPNv~?>Zlk-$ zOu+-g9>QU6D$5B_5_AUlI>DbB z6zioL4)#)48{>>QY1q~ig06eJioJTE)qc;F=T5}QOp9>N+wQs)`FV=gSq&Mw7pLH( zsv~j=%46M0m(@S-|4`3{WpM+WM7<*52y}RW$jPBOAGa_~S%BntKd5NdNQ)IXyETvo znm5>Yv9C$47gzTI$o z7XZ2o4+O@ggq@fhqT5nio8fw;MTmW|wj7ZCRT_+TOFk|t=tz3v1wq`Dj8_M?C%_8( zYEr-qF@)Rct^)gBJ%2WG-8#7jbK?Hgqs8b@BBNL|n8gRKJOuy>A!^grQQt%ho9>-fL+yRrw=^p?MiI2;==T?q5EL^v_ruQW{b8hpb%<)GP*^bt#?hl z?3^U{tQ4fPE+q^fGB#g=GhE4o#DVlFt`|O!XJ6W5z2^(0C5Tgj7Q7^7GP%`7X(x;Yfl&*4T_mZ63Rq93j{VO)K!bqQj8N>sh^vCg!+kvT zjAbqh{LQO=l!PkH7*5z2te)ULVJ}Ao6wo+hPOljYvM}GBy-c;)}K$9VqhUuXKamKh{4_3}>D;RdpA+%py;-hQWa@@}2 zMlBzCsf~9_1cS(9*qgJ@tAO8gkxX1$C(j^xL?|_IPR^v5;_@QjQy$e(aZr_Lj{Rin&ewTYspn~FxDEMHV}^sE0l%XdojeLwalp5MHgH7j+Xd*l{B>wac($! z>vhb6U{&7#1T02_F99fbpb`YVd*vpA=J;Tjwk(9+)$QLG41n! zYR@frV`zT4^=>gCF}P41PwBmJw7h$>HkqNvX-~FlyyH6BQ3ldCE}Ng+Ge3L?&N<x%Oc%7~~t0j9wx*Cs=PVmxaluO?9yC!N;bz`&+ ze(Ip6$doJr>I|p>bMcTCU4cP-jRCO`@sJUO@M?#isO4D#VU#4Np`ADk>cj>yuK5|- zK6~QnoUf;gz&@j zrzg665KE#jwDfsXuLeQKy*2S0Oo#Sobng_GCv#Pr4-tmeIw4fDZ5}Ui!!%dE(L1P< zstqaX1BcWDixe^2-e14VB`Tr^nd6#{yJRLo61Dv!tT#9U8|$TW%rsf-G)Nd#%Q;Sv*50Aot7 z9V{!ShsC3~Ma9#FuLA=?QV!fA&bpGy=hoh=GdwF|?80CjP0j!ty{kqkO#lbU+9$xq zrWG+lshPFWCSFv>Y&y5M_m)%@Es?=os9@lk&+)r50IEVon0z_%_`QVfZevH^DxQ1F z134A?sA3md$Z+FejmB;W^A+R8q<4e}A)xAYuGU7T$85Nc z75jcEUk4%y$JKNqa(jYoRaQ}BV)oqpX8LZ#?_NQq;wY#!9C7$wm0XvRI?`GAJ|u*< zY)LbUsdW)k&xxm$YHw2a1#E^;dWBw+slc2dfWH^)f$i$?9*PSjaUSl1N@y?>PAX|f zO}lVb__c-h^BR5jI$Gv%Frqh3q)JT8b*o2RaI(YVapo1m ztTD1Lv{bh*d$+2*htEo7 zMmScR9zI^^6cKT~P6s9ktH`B^>hp+8&|Miwv~9?@Dz;>OGMplP+z}gxpn(dH@y)Y~ z@+oQVr_9&>-Xc0t(L?oYk9#A5#yVjj>d!5h#T({Qqoa&fT+$RH1AnIAFI5!(7=R4Yk z!xLpTvUm(BS$Ji3)5%c;a$uFN^?TM;>=j?XC++Gi2TwPJADC8PLiOtzBwc*3RigGu zO!$g3&T+p}AcEoXo?WZMgnZ2Mc(+^q?&Y4;0cdorTOziuV9a7N=u3olax!v1+Y^`Y zBxgO2;Ry9t>eQT|ulS(^UtTqmQwLL}Q_Y^B?DZkcpJG_6W?Bk`v$EX%cmev=HF`dP zDV-hP$}!kppg|aX)bfT)@Nv)0zL9jG^4joL)_rlx_i9Fj;|XCsV{8S6Yr(TQbOfEq zTz!W@P+3oJ1l2}Ro0s#3fMhgpW;aFAc+R+oc{*IoZQZ03Cy;{gkb@g$GEszpL%O{#7|&mE z5h9=Ff=Ii(f-v9CR73FG-om%$O%!0*3F4QWLM-AQF@`&&{ZM%pI-cS?us&9~cnyi+ zcEC#O?FguNI}%w6p-;XZkLK`-=H9+qXT_U z!4R9dk>28wD&eDTD99%?Yg0J=5}X~y@RTc?t|vCz$ZCpTCJ)7%xezvdih(m-@7z-i zq_(5h+Q)rE9`0FPm=KDb9;He4a7PD{xpdt*n&PC-7?pp5AU?$~TlquHgsYNK3)Oq; zuhAMT^NrH84ZR+xrCB2)^*GwKh~UIJ7Vy_R_GHJ0@qu(n^_(;@p4nv=PO_#{)az=F z%{gP9yB%A^t-ezR&>%r=( zx@T?Nd8FnGQdii);bEJACwiAWd9|+0il{EFXh{#WEe%MQvmMnUPJpRVjN6kiwR@0GCmg(;}8_f#fyQ zGsq1d7zR4a^I*;#5)Xw^O<41L%Z+i5x&3v)Ak^R-M_+mL+BY%I<(5_pssKc0psHh_JmkGm_+U zCcg9#*soccZZ1zaSHv%hVT*if1@(!pPnZG1d!@d35Kf6aHnmq0lPzEb`et3jYb!c< zav?cx1XC3+HPiVP!F`JtE|fz^pQMKuPy}t)W8WKDRdP_Zemm%LE$X@Ai&BzeSvSt1 z3c(OhuV$M^7MX~uw z$2cjIWOOua?R#;_!6nB}Xfj?K`O6~r<^y?wxN-Nqn~71{(;uiX(Rfc*puBD*3_P?K zn;{xhT(#9yK=;D;w8$tDjyQz{!|!BX@r#$~qY3;BS!pf=Svq3dG*HbHHK4;tF`QFX z3P$>&M$YWKFBCX@Dq`Y(9Xj;XCHMkxs%=F5q z786ODAEFcm7d&>+eCagpGHU*6xtRG;h+6kH7dj6M`{vS}CdNBrdhsgX(o49fc7)we z4P(pYu?(YJd06(FWTM@`jNr!|V~3`i%xE~Mf&kd$*A_20yIZ}tL{*0Lp|kK%C2Hh? zcse1GH<~di&^hj+QC&kkW95q)dp^6tt+`p2xw=cK&-PCk#0$aAw1*)?m_4H;_HGg1 z!W*gf-x8hPTc_oR4SW6ai+iJuB)ue`&P zsS-j>(yo1@mn_0S>36^mDRTgiLd%z#D*z-T!-fFm#mVKKS;IAOQvtR$2iRd)JPY7z z(aCo%kYH*X*ivTW$=G>aGtzD9`yQ+_n=1tM@{{VKI_;y|=2e;{-0|81i)=W2 zr|zU_INmWveo-swg!m$M`{lZ3neo6lv9w-V661T6OjG5OBhT1D>rqnG5uQq%`_c9sYTB=tk#7=q|b5oUHPwtFu3wJW8mPWBZ&s%*ir2$d> zazlIc9i=?-aC;W_K>JD1&8y>)R3>&#L6N(A0Z&K;$gQoWq3FgqwlJN~643(kdWNp; zT`pA>wupp^bJ=5EvjM{=`f~FQ`!^*TJ{6fHkEc10Gg&cOF1l(NGx6>vR1UsF+dk`n z)vJE6^~)C}lD)@L#~e$$;sz=xZuV?Qs&yhL(tNrlhn>Uhiw@t%y7jIM}o+!1;@*0>C)RbzN z6N#a;>{kgFzmai2#NqY zZyfI`8J@xt_H-HG1u{WSaZ=zlx1$&F>K-CiShlQZkUt`kXN2@n-pi78aT3L+P&Pd{DOj9RKIekaR-y06G6qb?4Y@&^x>9CkNJb2L$=1I&h8sffgzX2rFxUz2U zB`?x`nU8jc20ZXEjM+4v z1sBcb(@?~mu(MYdqVS4u3d4_XXp6BJ1(cuY63hB{3$}<%dp-={jDo`)*t;w<*UWyC zR2V8?8%NYhqWtVn93WV=@I~&6n&+>{`nVQl-cEB#KDZulB-97Js~z+-?}>-jJQ0Cz zD%QrR5+8q(hN8)t4Y~G~TmdsRbEkQx#e9$@*Aw!1l^~Fd<9aBXF&-zzj;_3I6d0_z zx_yRLQLLl2QKMvTiic{ANVH2Bju)klqsIHxiW0EkSmYw1UCUOhXlJM2jBERzp4J&@ zW%M=YgH?EDanP+#7mftU!P=`0g>?oORvtoX*3{Ht+oBgQ z({~+tPR4b#@R)MUZT)5GU<39s9~C`$Pa(PmL+vu=FBvSKl&P(V%Yk|{09%QyAb z%bZ^t9WF?MoTUxMwfl)_0mr$mi<4~f)DALcBRwp(+^L)AQp)aSiu6F2okSwNz@7|2 zU;-b4k#1rX6-x6`0XC@e;9Zl@Vzv+N*Q@?rhfGx#!Vz;JZCIGtq_^B5CwE z%AtIuVOf(dn2zq4(B7Tb;gI#(;E8g-T9jLEt~i9bdXIfzpzG~(w;Pu5#Nq5o4-4je zcP$Qg@eHFw`d%i#X>nHLF3G4CQ>hr8IWZ3l9F+5!c#?^)?((Wv!JiceRM{NOq~1$p zYOf2;>Hr$2kw^>NH~JhoJ<7o$pM!#^_dQOBTt&3FbzZp30U&oY@6uGXA=?|_Zon`| zPR7CXp_yLRovP$4>wp|lq3|KoV?y-k^~NSzq<5RE={;zv*h&ES{sIELd02kCTqRLF z6yrV@SZZSWlp%tLk^pM#NhEF9>^LFfn`cCE%_q@zXp^-&gs>Zc<1g}I5I<}le?Etg zJ=IUM;`y#y-q^F5ex^DLh6q&13=9{0?@2s~NVwGA>32PmW?Xx&Z{7&D?-TOD*#p_) zmbJ=jZvcHGd*gxPzRH9sxblX+v>SoP-75YxGqh$>_u+Qgz?j{tVE2>lr_k7p#{+Ze zFO>4_?m;%R;-d+Bn(!zzLGi6uY7d8dBfYiG!V(BWLl6p`ddy*D!~{(`k83#i$(kZvo(dYuf^J=;~=|qFCdDr@g7i-ru%X>uEi(MC| zE2BXetlx!9`uJf(p84+E@t2eq+t39DL7}_)GN+u+lXpF>yOSDsotNMx#HPc0zT&Gz`I($4_E;9|*6Ilv$`nP0otyt%w1*xT3E< zEl#*R52@}AKosEQSdbf0e73nXcphkZCG6WdS(6A45b?HykeiV>JV~Lzl|eZ2y{<=X zgxPCMPhR?amM~8}hFbwpLDhV&;^ALNFb#}SnCB^GXxkWY9TBkf;K3@B`tu1G&v%@b z^YGA2<35Xm!z%<~n>L9LrZmx@$ilgN;m8ag9RuDJXJBD9E~gU@Or7Dq>iGcbur0dRc&3zyy#Ei=F|; z%hY9j-Rm0@Lfnw#P%NDj^js14=(0EMPB7wdd4Tw#QlW-o!G$mDx}xO19^;9(Z;l{o z4xp;?hbiBL@axx4Ie1vE~7L|IO(~f%ERdCVF zTINzG4=mIfSKQ6ZsKtl23c`BX6mK?bQD)x}#$4s0@$1;ukkyMLj2mp)F|tODLZ)Cr z-1yFEX0kkln6n1f5V77|toWFYOHMd~7U>J|r?tStB#I&-3bI^W<&TS~Id4nWs$eiK zApV5HOJbUPjV=rzDzvyA?H3#`FZS%Rk9E#o zZ$L((#XyOZTzPI2CEg2T%}~qdYzN0n%|12taO;u*7QOCylJ!3tNU!UwGI{DHi8DC?IYNU1UDKLa(9e)H`N=I zy!ZeqMUUsb6K5@6;2M;T+>n4apk2!))^dfx;zGv^rN-$Y&0&{JcJ38oBsY(KZk#3V zUIzQjmq3WK=YGlL$-HQQ6UC5g*qr3A=3dVs^H zNV10>yDwk~`pNV=rLyrpjE%97*m|dEK+bQWJvP~m87ms?QXL?bnh_6&?zl+~*Q3fG zj6yx>!W_d@aiV}Vp&&Mi6joCQ1}W?)T?!nXX%>XL#~dmWw@-l^$W!K2$#3k;9TuFe z+@VpQA6xBlsK-DPHoIEo>2-qNriX**bfKYk2r*_SsZ~!@k-ZF>cfG+@(pd(!#(XGO zBel>>Q#qGMEgf=$uzelO1OY&j&lV z`|30>qP6!GH^fUio|mGxl0*q$V#i~%D{}?%m=H587gRmj!mvV2b>Ha>d((+I;{_gY z0_4c1!M$ONeEMKoddxSoD5(I^Fv`|^FlNqRf*)CUo|&L+wFt$#9>8k_7#~)xSW8Qn zI--kiVT_F$H=ems2uBo{$@8(>%49M=aC<5Gde z?6yTq79#4n*t@_U(8cby-Vrj=YgfDB*xp*LbMUvGe_2sj68J_e}3ya^seiH(5?oamOsz^m8!2f2a+dqy7m#8gwbHvLV}mDn>qwr2F2k$cdNtZnh8%a>nU)ANd%!k z6*KKBj8>^^o#s@h5N(i`E4>NtaCuV$E>?!>C_&Sn9_*D)mXLr6-KFI;n{DK>aLb}! zjPlMNpxEF6UYgfEM^-=Nv1kegnO9Q3RNX~XBW!v7m>#<~OVWxCT|~Hu5|U%d9(6{gwevH|fUJAbwmJUpt+mdot&zqwyel{yeN{e>1qf2-=>`FtqM@)I zQANj8kMKL-4lBrKK9Bd+?It{kOQ%udyj9tbwG#ocVC7o# z3gv{`9z#CvL##tJg7Q_<3w8n?+N>j#4JmtcSF<`nxP4Tstufy1?+6EvBdT&dCqspm zU=3aBv~k?ui~*b-7G~9v^NC0Bh;*zIW~Z~2p9&Wk96x?)>ViO3M3LOXG#Bi_8~92& zyHGM)<%UD~@y)@jwG3TU?9v1vj>o_OC__|(f{d0}|nxgEwN({DTW zfL~Paw3f2ji%nE_@zmwWMlf6hJx)nw=|o;{GuBZNM17$CIzZdYbvpo$W_B3WiVmPa zyDnREm+UzR7OmVN6kqIk4U|s?6Hq&mi4=8G?#w|6SNo{aL-LY&QdvNQR>a~NWBw2v zKu@Y{)}*AN(uIp3jaNC~8sJ7u^KIrxRAU?BW1#{bqD@i~rMPlO4V;VXJB3yd|9|-$yhjm&*i;-=XEEV zcB>W|{9dn9WDG0#0sVbMdX$@}JCN;pkJ19M7Ep%yf{y4#4mQA}sz_aSWwVJsA+REG zc~-|3H<@g7tI*Rc(L~LIQ$YY?Oy1eW06K5A=~>Yg5Y>|N9oQB_GUxJ8Y=#B*i;klV zJhj+k4QNJwuP`WHsPF&`^MGD&6v@i%ru2hucsB@~#-@B!b@zkbFMt?@VKem2*1hSFYOQ+Tfw8K_H-&kFrK{Pw~a7f?H)i;~Mv_{65( zy(*>Wkx8DGaa*(Wz+<`CVxkY7&jy>(OWFI@R>9(I8oNpEy(L_IV8BLN%(u9dYSwwD zcoU-Tw0he0%%5|D)V<=KD5p33K>V^u6y&u?0nKBn%n>SAMrKApFtx1B2e?n~87Rjl zNda4=YEA`Hv{;(0bD413((~oq0Yx`{Jsi0Qd5FsTj#BC|F)cD)3!YZok!ccyN1@em zXH38Bt>Jrbe6zsc>btv$z82|}wdQhFBoaOB1Ib8Qzp=naR~Y&%O&nn;kNF|ZO-jIH zxq7eI*SF=wcX0%3cX_lHYR=XNJ-5grWE9@wCSE@o7`9O!PxpF8m)@-$4QQ!!#ElGz zy#1FVo7saCiXOCx$O7fEodMQGr3%l)?KX;Hlw<&#n>56uVm!b`IHQbi*6w;I zZkVaiJ>I>z*&8cbLo4|NgXpJM;6C$q z7Xbw)*G(p&VU${;X$AzEThomJu>hSP<~8f1EFecGtT*}6EBSTk@)@{tHuECr1rs6& zu5p_+zQ8(}3ZYmHQLEEL?9-rERXIfG!CEJd+@Lxxs6>M)NP++qMI1pnKn-(De2_7N^rpHoVi33ebipcw%J$A%;l2IXXF72HlQP5$>$WL;^wXz zr+YkG;sf3SonWFP`;KEUEyrl#plGP%ARznny+Su{2_#xhXI~H$l6aertylNw&hUCH z_l3y5etpwT#yJtodM~aQ?CooCSvYvlF*#k*-^<63g_RLl^+j~209H!6Ha>8n2lECH z6+?RnS8dUxE-N7RSvYI^-6_y&m|r$9#0G|kGTo)iWg zO@jhEB6>J3kY!)vP}=2TC}z)sf}Gmw5&Pp=a8Ve^ELK0VcjA{v5z zqA^BQd$wcX4`01govFypW{E`XaD82(#_pmR^B5{?$)V~ViSp?L>i}g6^AdY7CA&A= zyhlYkB?Ws`E6s~fsOe4{#4|(EA)d%15A0&HMb8P9@j#(7IoPY^3>p;7}9Ug>U3nWl_Ggvw4>jlz)c_SLp z282|6ZzvW$Ya@KuXqKG7rz<<3m7jJM5lE4Ac}6jg&0gL^3BA`*A^7eW>wbD}YIllEnyt2- zH2Y+v{j)T?>>SjF~iNqHsV*_!v-J{k|o)OVyh(Me@h`H`h zG$?(hDzKK&l~+jxz8^-lAq{ibOi+Ed^YAZgZe3>*3r4#wpM+?{y*h>c%LdyW>8 zulR^Q#}1#6Gp%b#$R&uO>Gw>SE^^lp+XWt$$32Mwc<6q-CNwk6#4w%D%9&a662KBy z;3Wo7x|ldwXca!5xlu0Zz|!c6dIa}uE@j?JhyuG#99Nh=LhbifY?8#|3Jz@h)Nr~` zAdq@pK_{b^BE`kZ$)5{>Xi5;=lO<&XlrpO~$01C}hv$WD#N-y**Rx{G zUck&e#FqsMCk1Fq(1f0tG$)O+dY(z_yL8lO*t}lhU7RmOFCW9!^AN?gPJ-Rc=(A9# zDa%)I&x;D{2x=ttb?A0`jq%{ULBbazVXf*;o<39$dNFyI2N7si*s>pjaBd z?`deJs{4Xh8_1@e0fIV@0cbTOpjBlSjv_{3iAT}X!ys*c%?6Lezq?7PG6ZF^hi*8-v+|e~fb#$nL9C<5jsWCWO4zPNDa9dFG`^r`*`8V73n-dc)1+a=qo}`ZBx6dma)K z!_1gMwFP>zVxewll>)kAiSO*{nrG9U35Y~kR(eB=xp`Jf+9w?BQ$FvD5fBugnhCOr zrFqDr;=6hh@$y|;!0{>gL{Bj%1ddd2xv%48^UX=` z*+#A>izO2Z1t3U!{vQ~?F z_vGocGPym0H-N|Q&^>DhQa4ghZeXz2MogsRE>#QipdQpX=z%XNifX_(W;>#pchY3P zejF7F@Br<4gI2(nDul8xB9zP7{=x$u*c}HV*=!sxMKmOV3p61ND!i6` z57!kBtwI=9>4ZyB^!i0;y?LD)^mL)il2c5vC~m<6(CEFoM`a~5mB<0Y=uCA6B;Y54 z57Efr2|f0EbOh6V&p@&K92+5^+jm^`DP0NV=uyJ6y_(E1Q`o%ThIn4dfJQ0MiV|8T znLPHwHNCPc6(?sBDad%xoXJR#w0#;Dx!+b-#}*=_`s*@Fdusu$zKG7wgv@wn&{Zu+ z8ABdJ9I2E!n!%p@W-Rbpq!w>Gd zO_XkB%Bn|^%6aEFLz2(u#ZuNnQ?#dP>6J_gPC?|uWFOksV6LT!M1qELKAlhYo_n(@ zVLcgnNyAT06P+5_C3r=o%PC{OIG9+ za}-6gt7G!eF2!E*J#xKL9}z-qrSILrKQ;9DFTz7lOjl()0Y#2N4*Igk=}0pI5qHGY z=iVUdJbbwHNUV}icxU6;p~t7=Pz z@P)Bem5|CSju+5(*lzoJ?lo7)c;+rdwO?JTJFUKXWbWb!99Y)~csSllx-_t}PM!nO z4=ibpFUOSSg~e9Hy~xY$oxxTrAJRTDXW_&uwKT>y%huAO3pqY`r94@U!sLpy$zcL> z{B{En@DL?4vMTPB>G90QWK{uz@M4V?S5I3IBy&Fk7`IOI293u9^eD&NZCN{Dy?GEI zdt!GU?q7iLDqMVc?Xd|*W3lOv#!WZVxU~DSNMmi;oVt3*0Nv!UR-)j%*!zjw8UU8A z?Vvy0ryQWM&JR$W>WRLUV9Ap{Bi?t=3IWsp97Cf!q+M}4ce}7B&DU=cqZA6Hh1t2X zL$J)8ci_pDo9~N<5~wZiS}ygt>dC{~t!*kd)k@@Bwq01q$x*LI9w20vH#H*5NXm&1 z9fx(^K6_P%SI3x6{bI+R6q$rV6*(Un!8oh)&`Iih`H1c?N5tvUz@`9vX?G;UP#?zD zkr-ZKZ#ciKAUPzm;&7V)*N$dJb& zXV5!q&#cP1Ev5BQhtU>MU>Qio#+rKUQ$9DR&|~tZpe>Fy z7|mA&sTuDaZu<2*gS@JjIEUM;#MRI@9fOYo{mu2=5J2c6UUSl`V(t@!WFC@j>bfT3 z1^0y#3VH*xJm-|5GyyLRDP9>*NVCa2MQ4#Xy*Rl*ftO%_SOEs-8?|yWxDuhnOVv9j znjG2I^^7R=EPL*XYH^uyeS8PReIN))$VjJgV%SWGJ41D;ZGcbV`EMg`& zsFh8v(t3Xb;!{Xd;yV}cVrY78%<)Q<9+AoPEeATc`5B?b5h0hwDv@b#`J2G*ER&{r#21|I{-#nALZdX?276J(r zdy9O|h1Eb;*Y+k~G>zHjF4+F2C)F+9 z`exxmg+70a6H-_)qy;gWZ((Q1-m`*vaj_h5IEtcky(q$~ksM{JFweca^l~qf;Z!sO z(w`()%Ptr}JndcNyQjfPH3%~9(eCM;IM2|Y&A@fT%wXr+4UvJ@=RIi`O9@a+`dTAf z3FQ&7Ox92+3Fo*z7B|wt+H0;R*<&*~cn4^eZ!7LD-isHH>1BxmRX!kotIviU{4ogJ zDfXk1HIKQsPI4pKDz`NNxT~Si=CAbG1*C!7?&G1CE(2o@>46w5M~2bTyA^Hlc)Ac@ z!}W~4M+OWn8_Jzxy8`SSOMuc3pqV=i0_#$`uWu*F|JZX(uAuMFQMc!ee)=$N(c996e@W)jOEe1ZOb@FG-T(YgBS_G zxUSMQwBd3w5D!xY;WJ3#8%N2^*RJU)#rAl znhdf1BwzMM8@7ZNwVCO%d5-vr5`1=I#;jCKG<7hI$HjO#HKtb>rF6$_QPBrTWN<4L57hG zhfzOv2U}q9gh87DXEDLRZM5`}qOoLgGj0~b#{!YNu8K}`ou;H?Or<3j-aPt3aBNDH zVepvM#tmq}8Sxn$anNJTJ@P9PJAi{-P1QDfhvY99OlO}QIO6(ZTq1q-WqLu128c}Et8F&jE(Rl=dp#)J{RfM$gNdyz84QbL0*yY6dw?oWS0ss z_q)lhFzfVnB=mWkAx+`fYdRAzo|;$7fso+5;^bt6*;Wgfz6{8+iSw~r!@|UUudZni z^d7c#!~rfMC}d6Gdoy&GdeE;J-k`J&ub<1~lw3W0?4@pw2Is|T6VfgPEN-B>#DJpX zP!HC?PuRc#k5i5{jc1pL-WB8=u)Xe)c=cG{=7vVLAj20V0zM@*_`uyPr7rKODEOw@ zTXurR7ffU5)_n(l9E^R9w8C;!(Uv2-Rr?B1`4zi%o{RKrqZ4mjUZTKB-#1DVx+pi! zA(Siq2m`SIaufu5Eb}I_qu^92S2K%6Fa}O2Fon^;hAn`2jZ$+QB$p%{Li51_6lR;VP&{P2r*A!;u{?ye;o-gz9I5mJsgCviy+~^AV%=S#Ry6HG-036ec zl+^*qCxzUpPmem#MFJ89m&tqEP`SpCq)s8P%rl`G*>9^9v;`8FgA_tuV~sYeumdAJ zc}OVBVtI3kE}}A_rS%Asi*#Y?9!gH;k&Rrj zF=AT3vn{=~Ft)&*Lw3a@NPgazaS6V(&CQ3cEqyN%bj*w;a=Xc*4sAVBxJ>A}j-1s= zdN(eexa$Syl16w?PTQ=~gdU!CDHz4tsJ)Ca0P~9gy59`uIBkow@#C#2kBS5{eeQOY z-0NlO6bE;+`_SK;xp--`E{FK$r7k>beE^~xw!|dejrP{m6GJKm4jy?9B^%#Nj&6@s zz|)5YGw{@(o<{e}UL2dqtc*i$qt-*@9a%XOwNQ()eB15F^r|Z(sClt7N2hEez)N(I zfp-NQlredp;;=i5B~IGN;z87HOg&oa;o3*#m(Cpoh#@{=;+~-6b4U`Pn1D!#FNCyrK&0hDDE3Manjo`j$# z*}(F$b#NDYRQ4gVt2F1Fd4D?@CJaN)d5Ryy8e=3j6@G>zb4N$>su|Jjejldskj3sIr64NUsf- zG&#~pk&R+AY1@fGqp%nbNuXa*mb)~(R)&f*b={GRkY~%-*JGi#+)uOvjnK8DaJx5r2y7H-(nLfQW|~R50RnibfImFZCY zXVJCC^d3B3HG2v3%hmKL!;l!0zCE}HUESkyC|ME;4c(J+$dz#LLfqsfaPV*m?CVx* z5ysHE2E|iBW@vSMeL@&YaY1ux{KbDETzsX~6Nn{NP;5>C0EbjPmefGj;!j&N&I za@#kAlV&@Oc^OJ?%y!@Kj&Q`Q5#)d*o-wBBZBd&Iu@MoF!SNZUS|W&hlB@Vk9#Bmf z(-AzYIN~CnG6C*4c7vG_6{Ks=`C^~Exwk|TH5+3hv@!XBdwZri|DM)W&ad18~gK>~NcTiFPb=hLzezO;@0R zGF}Oz>GQsD9l0P3Bci%387oa|b0q^S+Xi||C#|nj6JC%L5;Cklws3yOed;Nk$hnY3 zBINKS9vqaOf{2xl8_=%W?nWB#1q=a zM5z}V=j;(mHncBgM0Rl9L@|XmUs_`TfL9;Y_9NOFoDq5a#x9#u-fIQa)u#rhx>NcB z^40;BU|)4-6$>m6b4HDy(r^K9+;hm65caa(3la>_lcP6l$gZ}2!1GMm{a&W5z9}yy zer^n$M)oMHY*8M9rA{gwwC!bv(3F8vctSYI%FaYk(3B+VyQLN77fvD9levCrYZ>5J zzQ1m6#4^#sFw@iNx3NTIzG)bH<}d^#9tWx9j3pyuysi7Hz^Fu?TPkBzYo19A(s{d^ zG}&u>L-EGWLBSc{R%E<1S75J;)B_sCWbM6Bvs zPxLX{@!{cYDV?%_Z!HG9Owm@jf*rNnY${wP5fZ``SBeqzGCW?e;zHu*%J!mnS z)IrgM@j`{79OwM?pzI7keV|858Ov@Rms3?S6)@@d#%P0~1tqQcF2R^-NIDbRV>eb1 zN3~!3?K`;pM#ZPXRJQ32!mj-8`$lWq>JZ_sgaiIuf<`VG#KaM z1VmC^<3WYeVy9$b(Bk976q>^pH#S&t$1CB<#e#8$gZ7KT0&ZogL||+ipSt&eAoWou zijo&iM80N#JJX{l4=nQB=M~wJ?5Z~AuP*s~XvjUA1uAUh;4GGf(L_kJs4zuC3mzRs zmSxF3pW_EFNUOsrsDi2kr3)oCvBnaTk-NAoF`M$#Phcz#I@$<%P2gaACW5CtS`y%BB>r6BxbbO{uRxD8%C^u|$jU0QFu?$knkehfk=3Ec$h|y}>MbbK7z!U13fambGe&t5# zetF88drIn@RTNd>nG&95YS}y&+H*=cAOO{h)BfJQ79LYP=y z%7CJ0DEXBF5=E_Y)g%|4fkzm?)uhNEk)15jNde4-t#&^2dHA->aQpUIJ(cub&yI;B zuDoZ_97X6`SIJ?$W{;?|IRjbuWX>u`?WG7MZL?0^df5ftBSOWiz?16qnBtLp&F>Qy zq@R0*{^lcMuJ)lcBRYj=N5|%2A`@K}ch69$gR;pheX<$MMtCN}p3er@Moribc?Rw> zMJ{DuoaMow;<#*H^F4S`hSq_;Suq4GOz)RUX((Rm@SvfR7c8nnN~+-+m^0#4aB=rg zjq1Q=l}uGu0FS-}FnP^O1rRu9RDImY#bh=~dw2f0ur2s3pW@x@iw8pk zl?NN3NUouMD`iWs6yR9qC^IR7-cj0INGV*p;mh|7uk$Dnd#wbq@K0Y6kjT+oR;e8B zegXDy<1Vn9#A6=>7g#`qL`S0B0YWQ+KR!#29O`_o3?3VlV_?!4sz8(_vhkGMaArh< zX+-@NTi~d-%mcxZ+pOPvyrL6G^SY+l zH`M@UVSzx4Ba+W35#G9nR; zi>JdUnmObfcvtcw*&p0f7?YPlW3^({?@S)?rJ`ip%iA`^Sh%NPIG)r2hga;%VIz64 z%W@L~#hI{R53pk8p4l1=`_9#w%z4vcDoGlV1S(2Xyk0URcfonN&epmWSu%msleQrQ zjNRG??_gDd*K@^0XXUxF+-Ac1Vaw@33uTqPa~`0uq)5A@w)JXz^~e^+%drD@&==wv zbv`Il8sajGDvJqeGLFnNpjn|CyS^gDeM5@`>X%>&4;#s-H)1AX=7Mlu?F@t@UC1&f z6hw;-;1jwRB2Mh1=Zpa!&Px!QmN2QaW+_wnIFtG9GzH_+ey3JgoG`nexZrQCO`}iP zorLEImd;I5edqd!VVzXd7qiqZMq1c4L;b~EHi-H20DtvTAEGVxF?umoz(P*DQ8@?*s~9V}?6Bb-D<70ML2QaPv{%m_mnuAxv=^Ts(Yt zze?c6w8`s5ceU~yX!bd0)jfNJYuTc9uq6T!tM6@&N`| zb>;F&E4!-JwOz`j+gig6JL-!!H=^~bi=2w?!Vq1`9y7s(gdPb*o7$p;8!2+(@HWe( zi|h8Ihh>4HU@j|5y+}E-z9<4oF91!l?~QrSpfzkhkR5BJ(7KogXGZl(O4Z^nzYiLXn)pNRM0;1a*jmVJD)f%z z!m$aTQS>s&Sq=@D%k(Jwz7z^9WqFcrE=LQ>tgY3!5Os;s)+lX7ZiS$@wNAIXG z3=szBd-&?5oBA5D0lyN3Q!n24mfwz_V3U){#U)E4Q3ed39$JsoVNAy&kj$B%Er1y# z)(za`qg9h2_q=9dm%3-zep7^+^vc7VMyDH$yeJcxvIm|0%35z-)0wYLIfRDdiH1B_ z^Gn3)(Skgx^|k5(06kH6bdR3dXKrw79cw@*R@${NO7`y2d-Vwn`4rr5r=P#O3aBtd zp8?8DphA`-r2tv#nl}R5qw$KY4gk=lRd8s-p0)-Inb9^byC5vTLNk5^1!T51tK}!M z0O`q7Yz)1tQ{V*hVg^Ie1aKNRNFtM}57`#dv?$AX+(7}Z)YD`afNxYHJ8Z5!;Ucq)Uc^U+%U0!>n*rR~w*K*r8 zLgjlQzK5_23SM^774ax148*rV%>cTMweeXh?$Te#7;ZcY2p@b@Fyk)3cbdhIm+)lB zp6=DT3P7(BRIb~$8g|&3`h^6wGh&G?ffjU8@DY`(a!}PZ9e7;wx`~Tl^*ZuwRe)UD z<2ajIBRe^@Cw->#QlJQ9LhE`&7ASS&Q2_$b>PVQTYNkAaCbEv^W_T4w)PDNZJgy$y zAuw@p8im3O>kLGgN3(}xuN3gK`Sm#kIRF(Ux%SKsr$IdqlMZnS^WYoZ*Nm^Hb?I%| zQTXF`&cnwwtTDThgHVRHmHG4@JHE%qh9nQ;5k8A1bu|-AHqBGW<~%gx)u8Kb$Q^VNQS zbL7(=5Zs2b8UmhYS-U*QKnsTy#IE#`#lSP`*)Rj0jFd|{X4~7@*vIULS8ih1ouq^{ z85aZ7pa1{`!~$1s6P{;$P>`NjHM;}Y+)e-mvLJnyXa2YztEz4ZA;$G(4z*?O-oi6Z zk*o&HI!JQ#Sk@lu6+K9v@{Qu~-Bw?qDZA-Fvqv!3_KqbP(?I$79ko15Sz)7kr#{$T z845L2EMEI+?=|V8B2U$)y4jo59n=uVeWFUw)_j|ySA=odk>Bl+L!O}GPPCFezCJF&qshM~yM1fLWzK%QE z+0zK2uk6kr8=tf6mTQJBI7PGEM2ejOY#)Rs|_H)q?&FCytg(nE3_OU zPeh>tygeRML_ogIrxq?_pN=0HOx zD#)B0he!BKMWGy6e8vns$`Y%(V{N(O+;OuLc6xSgR&^>XRZi&ODAiWb|deG^jCQRm~;qYeU7`8 zxEocVnWK2L@cKBV%IYERW3ruxOm?~EWPZ;J8J){!Z5`HGoGjU?p>@vp5vki59$Xm9 zhzlbg0+DD!v+>0vXP{*Wg;Ppmbc~=Uo4UR^N!Y<>-gj6|`Sy~R16US%iBnn;o=^lm z@3FH;T<*4b%w`}PYgd4F(Hb;1i9A*tRrF?JBh_Bu0ZrKD+lqWv7PSLdTQ<9l zF40J!K`|l)OBge6VFB|Tsz#Bq#c)# zlbpSP`mD${cVCX~u#qm_gM{#z+*W+Ki}r#EX%dx%#saX<;o4q=z6YAa{-6NX_h2Hz z#hl&~T`6;3Pmj5oAwOM|S#V5$i;*uuhS9#E;h%d(XfWHL2?yNEj4BWTcSmz zJ_;dFx86|evro96)*Hf#<=JTUj-2CI-i^Y3E)Z(>;$*;$oSll%szU3f=s|mSD!meU zAkh+;O{aKPEFG$4aySu$cf1nHpk&^=fthJflhV0~j%=f+R-cv_OG1Hya(eoRu2AM7 zym>SebC7g%mP#%Vsiv4jm;5e$rp6Z)B{~SeOUP6Jqv>Gd5DJ-lU$)rE*bBSp2#)1D z^)eWuI5z?8brC4Dcl25(J?Hg41V|4VFYrs;&9NraVlaa+pcaG@IJY%grt>voKer~Q zm%_I9s#Yu_Yo|{ZkL+za#3MQY{qsDfh$y-5Mwp+Q4fs-|*qjfi9HNa_KXq5M^hxCl z_IHwwwlFlej2V4LgvTRGk71I0naav7p4^hT;OlWhd)L(cC^S8L>I4uv#Z-aBvCign zOkVIKgD8S2M}1Ins!)>YZ&N#5MJJyX8^ol`kn%R5^xMpPnX{9xfuV9P3-zpDF2$rK zY9NRaU|G|X=gvK`3l^*HVxvQ4$LAuV$S?)O4-aC)%ORcL6M!U+l7{iSdcs7>_^?d< zoeP=(1i{KZQ*Cu~RuSqr(^>Lg$l;j|<$D@hA!b}7TgGn&e z@FE8Xb17WQP_+ZWv*yCLnvdX?v6Bbv9qIFVVE%e+mNz_K%z@MG3DC0cUYgr^!A9X) zyK@D;7iL1Q#_&oBA2fmniVEsHm0J>tLJyS<^@^%dA$E3EXIs;!Z=kz&{R#*JFk0^0^4EUhS$o14n^G~qxLOiZVTLdWqH(3Of{Dm*9?RjJ3GE1M+u$gU=4jfs!kN^*B3{$$W8jfw7Q0QfP?< zdgx8e3&w)3tSNy>-+Y3EEU4U+rPVc0u$g_{aDiL4SW5JJE+fi^I>|e-3bqp#5)WDL z3ijw|iomak-<)hoJWlo}dTmO`n9tmi+u!IYv5T3oxIr$ZQ`;lRhLhhAvo&c@)`S;r{FQ0z&@n(tG~cORY!1n@fbUn_SyDpw5-ROq(&A&hVr~CJawW1 zSA;}J9Ry^oMQrZLIA92?6A(U>0o3%gVUL;ecJ~kU~}@KgA2NmfrSZB|?*q zNB{|#sc@UOKu_2vqBXGSHNG*5XZ%uf*#@X2!Bd!py=t4KLL6qXx63LHG~E?>G7)v` z0ZjWk?onGy+AqM6XkMz`)g{TIs^3mD0?VY?Ts%=e1#lZQI>RzKV5rt^>*xm zgxa+q_CsImtQ7JWq{m$~XUCZN@=;VD=`D(vjTzSV{UT;EvkA|bCpainA9Ycc38TlA ztockZ8aCEw4u#N6>hNR_mDrFKmeFOwyv~G@qSJgGM~{yyr~_XFuE4q5tA3^|7#eqE zk-68@hA@kY&yh422A}#MXjSja>?E#u{ExVFQ>_Ap$tVE{*@Vyly&$GR@TE9qK1Qo$D%e}gb zsg2l?tit!e`pD0&qS9>4^b+Q%F0((u;h!R0%N2&go+NT{c$FcGaq*CKni~U~`YE@v z7n)#^Pwyxo)idr!+Vp)(u&JS-~o%GxvPj|sFN3B%J01lM7OE8cbgm|0}_hxMlouh zrlMBeq!WpNTop_}aihavJ|d|=A>Ut2#D(&+fZ`Ro=q$K>HS>aG0B~0sBP&Z-%pC@% z?jG#8i68faTa(x;B4q4?^?2fT>*%&})nevawqD?&_&P}E8fc#FRjswF(V>FY2#(y5 z>v39^`p!ulxoqJJJVdKoL!*d4(edu8LFon7ybiJHX;Y;)gVhUD*V)4Hp3gHI$I`6I z03IeH*mH1PR35+1lLw2h$h{d*{e`5`kV=WAY4C6hIKGN#*9B5UO|S!0E(3QVQ}^M@ z!-eZ<=hjJ|c;>;Xsi23uPkN0NFIDD%Yqt7vQgUTe!7J8iUzZb_V8^L_%+qS+)$81C zXo5(?WAzwG0XLAL%;8O38NMLjXaVBhO%p0>vj(rm1{y)!f#MMWtI8(dH7zwMqug_S z)EV6)7W=$%Z8W6z$<>q^9Wh{TU?)7L&%3xys{qPmeuP-y;JC$w?YgcMY5J0~RqOZ9#jIo$!{+^fY7Lr*-6w zq>_VHCDy3hWwyLGR#K2Y3P#UKB|vn|nI8Cin`fY^=#X<$`P8I%bM&;{oha4L4u4@yYnB*W`ydfna8Q)~})M&9P^(kW$XCul24 z1jKt0G6hu;$=O_X^s60c93g=!cy*2RQjS_wJ+z0ZpKwtJDC(PtZ0~_i}0{Cm9a6ARFc{H-XvWt7~IbC@vnP(uk zOfaK9OM9=MzdM$foUYJ4C^E~@=@2Gr*y^U4Hx`~lz=^8g})I}C(;IlU;1k(`_8-j3M z@N!QKi(1y6__$6x?37R$Dl;NftiOH~cW8U^$Qf4icqD8bd3a^}ZH6xkHVC0Jypg)o z)fFRA$x?;|sMGT%Wi2{YQDAF@9VQCOvwP!n9w-T?$2=Oh7&Ie_c1ux{O1?ZZX@&)B zhJ>C%IomaL2R7QbnZW5}K-{_sYtVlEmhB@l?;L>@XiVoTV^G&$u(5DigsHJiu4*(R0ARekFawT zg71Z%Nvw<4S!TTOddFucO{mt$vW-uh;dEuX$F2Rb2Ls%BMOhTavONh^^xdJW)>{#l zDte|nhR})6llomoIpL*VQsGl_Xs+COG=Z*{?F1fp;q5pKm?}t%6sN*^pt7E)iy0E~ z&=a1Fzj$bRp52_CyGq~i?Uz7%+lQ^rAfxT@wdo38Dg81Wvzk$$LV00oqFhj zC9G1`teqK(ue-oaNTav8`eGt#NfszqtevhM0w`~vY~P~+)IJT5osJ*&$BKGNI8!*I99l^`daSbUclBsK(0Trke*H~K8Os9 z_PKtT?BglaQ}wiYO4zPJ_x4@5z|K5%Nmj)!4#Y#1;ZoXrQf>noIy85h1tuO5ne&hb ztcDMXQjPd#ndO#^4Y4#b{A}N!_+ZCA(|tRbxMd6i8%i&!dO48hjiBxmbkuw!VxWXP z%1#v`_Xf_8gaZ+N7J6h4-3NpA2xkwgW&@STdBYbjS>RCrgWI zfj+pi@6v9%D)-TyNlv{QcX?L5dhl+~2u;|{9Pepc+tdv(GL^w7=|jsp+tVQB^0MZj zR%YTE>@In<($($w5L5Z~GLG?XihN>xWFcb(Yssg32yN zMWE>zzSoOsO2m>7m|Na5=UHSuJfTgGV13is8s~Yq4pI1QI^1jareX$_$JN6wFbz*1 z78_*S+%vK_A^x6)tmhlY%ly;RJ;34iqS0Zxw-dSF8L?#tA(}LB|_{D8FJfueBA)AV; z_U$3`?gyqGrkeCjk$1EJs~{y`AMi;D?@U;&y_NG|dcNzL*fH;}R61_pHo4MO1~&On zXHTscM~5GTG`6!lnI_Xy3cV#}XJ->ep?bp^+{~|Ltxs*eB7!%>hq}>53T`a<9uPd< ze$iXUP{MI9NV~N$G|Oq*o5f|GFX7PkNtHy_<9gj1;47F>JyO+|@fzML7I>#EW#*1i zvp_(!SJV{0*PR9F+LF@L2&u${M)bb%@u|t3@`#^BcL@{R);3r=hQJUUm+G;zKh4`U z7CivN6kM!o5iuy~wr#y2h(@cE+MBV~O5ITJd+7O!xJLToS(dXdc`{Uuqe*Nm5jg^C zn#gIy9LcBxfR${{y?D2KWh4)5>kLY2(46?JE@4r-9!3u5$>S{CBqQQXRVG;(3cH2` zm1e}P>ZR9@p#s=+2&~uPZTo9%j%uuJ_69ZCeyOcTJG4^9I7mzeAqLi>vIyh(pu8Wj z`dtX}^$`|7jvjy0yW$P`@`SuzV7e_)z!(_@CKx-X3>{s0`=RuL=*r6ZZLKU`NsJmp zIAnr$dO|-qF?_{6D+Ba4UY5r}F8&D+ix+JJ%~N)#pX4QHZGoDEgM7x9=j z+XDem24Py!pS(01uK;L9#=d*3*bLqn73PB_z8c^t4GmTSh-Arfz>-zt!q2l$kP(n% z>lr@ZR(k^aRAq2AwR|gw%5g}|pr8ktfGVEyJek`?u4fj$?-gsB=)F3I3i{v$JkDF#gR~=aIui-A^Fr8d70;}GP;VF3hjP|v zU)q{4_ck>|P7-)LwpQ~;E)E`;ldLcblP@fJ%VsLo2GRE68Y@<>^|gr+$MdB7kRG6upcYQo!Q#6?;)@)Y za^bw`UgXA>H&BPWxbhO;%_u3oi8(CuHIFDDc{b@1hL3Zp-NII#9+C@qmIr9mX}pY; zL3o`H94xs;fM~4FI?yA0XynynC})nrR}630PM{v*N!VcxMQJ3@T_mhdEwN=Ik`*c8 z49bN)w3NDitzuD4&3tYiv-4IJ#T33g;@t5M!RjFuw8(S9cvp zI8e(Yj&240KM{2s?5AiAq$tD#)6a5pHW7+Nzn^$c3H*q$J}5c_gCQt?*o;Cl&Jw8@eqiy1HG z6h|74)@<>nNwk#QDHS%lDz0e(`W?NgVGWgVr_+E|v$sTYidIc@s5p8W&QIPhzSUO> z0NL^g7dom{YTu*rxe0Co;6%W@xY36Ddj|o`=V8~W{!)Wu+%G7$FPM;FJNaF}xXzm< zz`f1i3mPcGM_s44@i7q}v{afJ-;9nP|DK_6Otg!Y#R6R z05{h<$spq0#pH=1)(jD&2%55vwlwM0cyog2p#)B4=B`4a0_ZG@Z+T|cACMGKYBOnA zPrbsH=B>vA!_L`-Aj0tiDy~P`i+2f!w}fB54ka63Vs$I@tT%n}(x1B7#i6NgS2HcS z#j;~_X-))->mh=p=Y!G zpn25>!pR9$?}c0D>r{KI(XWiCUEyB5=6mQOR#x$_nbb-V7sqZ1`dE0k4k7|tvd4-k z(KzERl#ips+%Uvbe8zVWCjv-mC;s@&3nJjBH9WrOq_RBg${Nk2*KpHRF21|8FZ%81 zslZcBE8=+xD?3cS3LZEY?^Y`WCZsCj0*Luq_M{$y#si6sbBScrq`c!QHb0q32Ys=R zG;hfGEy=M0Yt5m&Cjp|boyeNvhP?d5dS^H={C;l>K_a;3ftnLWB9uDV$zHCmnE?DN={(wNxMKLrGjXfEkj0di)CkoZ3(HU%dm^@nX6lg_C~&yTc1{pm zKPH_!dToYBoaEw`%;~RXXpk&{I`i48w%_Sr5b-t9g4jj1n5z)Wt(Fq$?qo*J%@Kbu z5EK{^Q~cP}=P@R@MpD|SznLJrLLzymB-=D%PR<&YniZ5W?Yc)2enJb(w;4`=VVDp7 zCEAmwS10n~dVy7?<6h%48Fm(?1@aB$agoId45v-vvr%1)eO7uB^=Lx_`UNsQ3qrM& z^^9W9t=O(i0obRg+4S&Ba%a@OWrYx#7l&tJl*3PNU0ORf5N|9yt26 z#R0m|?K&2*JcOQmq1=>D5`uVpwUFRN$uG9ep~odzSC~WS`9WnWfq7A|qdjISv2ig> ze{E>V6O?)rIZ26o&3L3?3NY}lOGI`LM^Il8>v+7)YK13Qz#bOIvE(8*k{t3N6$T=9 z-bqTkDZ5GX=Htg;E?P28L6x|MH^41~j$$t-U;&+jc&t8lnSpl;GB4? z;_1b$+^2?O*`P)!QL!&$GbXt8idJeuX|6Meo%=v*O_uIaB0jhBSFMU(x|60bF9ydM zpc%KR_~qkmTrj9!*lnBE*2CDBT4|SA_GTQ9(2*0pH=NrtS0_3%9e~go{Y2fcy>Hyi zFViN6_^ER&LIL9_25d14Bb=Npn}WJn+)j&Yd_siY_aO__?t~1uDHCJkiU>$3Orlw5 z7y?t?Om5`3GI(haNqDRxBUeAj;sJaQmgF(+opi{11r}E+VQSnyhI-44E2@Z4`VfV% zxBL7CwAxn?2f$ob%-nY(v89dV@c}x)yhUX1cmq2fhDb|gB|QWj`Ydb8i(-aHJz!xs zFoG^#0g-UJpS&m7!n(>#Jkd19!hDaef>!d)JG?>>mf}TH>o6iIc!9y(UPh2Z!{+gf zE}5^w$2>`!noK!sH``h?8kZ?a-b$H4lK`I^1k5g&BsRB8LsRte!zh;6Q=`YtP?o)w-)<};FgGz8#&mp~Oe1+PL$*dQwofjnGBIs2?89CfLr?zw3f;(PQGwwO&0=Ppj#kS6Yi zyUQb26zrD72i7MH%%qC6-d~8>l#t~~HyHXlM z2v1gNfSaT(ygKpe8&RW-J_v%69FH1yk%(z@mgkdf0SE3|g9fD}6XK}!=G_HMS*S|y zFadbvA$8ot(_yA`bWlFD+>VVF76ZUbpVqv}sU;*oavzU@)qC$$)$obbLoneTrE#2I zlU_gcC?Q@QEfR`M!F96cHy3jfZMcetJsj|Ev8Ii#&`e6&gcEmo(~c12T9|f$(I@AL z9-SikrWqn9YNL=y@pH4psh2hfiz2x=t_E4XO)PM{Z@0tA&qMgpp3*!;B(Kbqi9_Hn z$q>s;(1wcgHZK@vw9siFftk%J^CT)gyjb;}r9lU2E8EvM!V8z9?_j)BEzM+_g*7iA z^xjUrhg1BVX;kQ$D?WeI!J1GBn24^F^#cf)d4Je zmR9p23sTmzfkyL0_fllSwG(~RrCN`O)l7z5OJKuab5KszwQxmJ4>^%PB27%FhXRR0 z;zy(dl#70<+9pOL(tLxC{bIHIW|cNc4suT1rLQ4rob%ONo_WlnQ|7U%ga*B5Y~h($ z)x%=E3029!9%IV&&M9+RwUE6yp(o_AHa;71!{$R|bPY(EIl#x7&<@Ge2JowglTu9I zB;%7BL`px7-A6kFdYLVzO;^ykPgD)Z(XO8mr@WJ5inF);c$Q3$TU5H!AA!xpp6R5N zHE-F13))+H?qCgX0P(Jl$Xv*LET*68X-chD!E(mi-mTpFzZd!U|KI=o^Y8aRCpfy) literal 0 HcmV?d00001 diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakf.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakf.go new file mode 100644 index 00000000..13e7058f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/keccakf.go @@ -0,0 +1,410 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// rc stores the round constants for use in the ι step. +var rc = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// keccakF1600 applies the Keccak permutation to a 1600b-wide +// state represented as a slice of 25 uint64s. +func keccakF1600(a *[25]uint64) { + // Implementation translated from Keccak-inplace.c + // in the keccak reference code. + var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 + + for i := 0; i < 24; i += 4 { + // Combines the 5 steps in each round into 2 steps. + // Unrolls 4 rounds per loop and spreads some steps across rounds. + + // Round 1 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[6] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[12] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[18] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[24] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] + a[6] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[16] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[22] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[3] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[10] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[1] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[7] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[19] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[20] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[11] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[23] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[4] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[5] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[2] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[8] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[14] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[15] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + // Round 2 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[16] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[7] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[23] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[14] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] + a[16] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[11] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[2] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[18] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[20] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[6] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[22] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[4] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[15] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[1] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[8] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[24] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[10] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[12] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[3] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[19] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[5] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + // Round 3 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[11] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[22] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[8] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[19] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] + a[11] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[1] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[12] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[23] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[15] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[16] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[2] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[24] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[5] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[6] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[3] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[14] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[20] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[7] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[18] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[4] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[10] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + // Round 4 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[1] ^ d1 + bc1 = t<<44 | t>>(64-44) + t = a[2] ^ d2 + bc2 = t<<43 | t>>(64-43) + t = a[3] ^ d3 + bc3 = t<<21 | t>>(64-21) + t = a[4] ^ d4 + bc4 = t<<14 | t>>(64-14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] + a[1] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc2 = t<<3 | t>>(64-3) + t = a[6] ^ d1 + bc3 = t<<45 | t>>(64-45) + t = a[7] ^ d2 + bc4 = t<<61 | t>>(64-61) + t = a[8] ^ d3 + bc0 = t<<28 | t>>(64-28) + t = a[9] ^ d4 + bc1 = t<<20 | t>>(64-20) + a[5] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc4 = t<<18 | t>>(64-18) + t = a[11] ^ d1 + bc0 = t<<1 | t>>(64-1) + t = a[12] ^ d2 + bc1 = t<<6 | t>>(64-6) + t = a[13] ^ d3 + bc2 = t<<25 | t>>(64-25) + t = a[14] ^ d4 + bc3 = t<<8 | t>>(64-8) + a[10] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc1 = t<<36 | t>>(64-36) + t = a[16] ^ d1 + bc2 = t<<10 | t>>(64-10) + t = a[17] ^ d2 + bc3 = t<<15 | t>>(64-15) + t = a[18] ^ d3 + bc4 = t<<56 | t>>(64-56) + t = a[19] ^ d4 + bc0 = t<<27 | t>>(64-27) + a[15] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc3 = t<<41 | t>>(64-41) + t = a[21] ^ d1 + bc4 = t<<2 | t>>(64-2) + t = a[22] ^ d2 + bc0 = t<<62 | t>>(64-62) + t = a[23] ^ d3 + bc1 = t<<55 | t>>(64-55) + t = a[24] ^ d4 + bc2 = t<<39 | t>>(64-39) + a[20] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/register.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/register.go new file mode 100644 index 00000000..3cf6a22e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/register.go @@ -0,0 +1,18 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.4 + +package sha3 + +import ( + "crypto" +) + +func init() { + crypto.RegisterHash(crypto.SHA3_224, New224) + crypto.RegisterHash(crypto.SHA3_256, New256) + crypto.RegisterHash(crypto.SHA3_384, New384) + crypto.RegisterHash(crypto.SHA3_512, New512) +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3.go new file mode 100644 index 00000000..8d775684 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3.go @@ -0,0 +1,226 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import ( + "encoding/binary" +) + +// spongeDirection indicates the direction bytes are flowing through the sponge. +type spongeDirection int + +const ( + // spongeAbsorbing indicates that the sponge is absorbing input. + spongeAbsorbing spongeDirection = iota + // spongeSqueezing indicates that the sponge is being squeezed. + spongeSqueezing +) + +const ( + // maxRate is the maximum size of the internal buffer. SHAKE-256 + // currently needs the largest buffer. + maxRate = 168 +) + +type state struct { + // Generic sponge components. + a [25]uint64 // main state of the hash + buf []byte // points into storage + rate int // the number of bytes of state to use + + // dsbyte contains the "domain separation" value and the first bit of + // the padding. In sections 6.1 and 6.2 of [1], the SHA-3 and SHAKE + // functions are defined with bits appended to the message: SHA-3 + // functions have 01 and SHAKE functions have 1111. Because of the way + // that bits are numbered from the LSB upwards, that ends up as + // 00000010b and 00001111b, respectively. Then the padding rule from + // section 5.1 is applied to pad to a multiple of the rate, which + // involves adding a 1 bit, zero or more zero bits and then a final one + // bit. The first one bit from the padding is merged into the dsbyte + // value giving 00000110b (0x06) and 00011111b (0x1f), respectively. + // + // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf, + dsbyte byte + storage [maxRate]byte + + // Specific to SHA-3 and SHAKE. + fixedOutput bool // whether this is a fixed-ouput-length instance + outputLen int // the default output size in bytes + state spongeDirection // current direction of the sponge +} + +// BlockSize returns the rate of sponge underlying this hash function. +func (d *state) BlockSize() int { return d.rate } + +// Size returns the output size of the hash function in bytes. +func (d *state) Size() int { return d.outputLen } + +// Reset clears the internal state by zeroing the sponge state and +// the byte buffer, and setting Sponge.state to absorbing. +func (d *state) Reset() { + // Zero the permutation's state. + for i := range d.a { + d.a[i] = 0 + } + d.state = spongeAbsorbing + d.buf = d.storage[:0] +} + +func (d *state) clone() *state { + ret := *d + if ret.state == spongeAbsorbing { + ret.buf = ret.storage[:len(ret.buf)] + } else { + ret.buf = ret.storage[d.rate-cap(d.buf) : d.rate] + } + + return &ret +} + +// xorIn xors a buffer into the state, byte-swapping to +// little-endian as necessary; it returns the number of bytes +// copied, including any zeros appended to the bytestring. +func (d *state) xorIn(buf []byte) { + n := len(buf) / 8 + + for i := 0; i < n; i++ { + a := binary.LittleEndian.Uint64(buf) + d.a[i] ^= a + buf = buf[8:] + } + if len(buf) != 0 { + // XOR in the last partial ulint64. + a := uint64(0) + for i, v := range buf { + a |= uint64(v) << uint64(8*i) + } + d.a[n] ^= a + } +} + +// copyOut copies ulint64s to a byte buffer. +func (d *state) copyOut(b []byte) { + for i := 0; len(b) >= 8; i++ { + binary.LittleEndian.PutUint64(b, d.a[i]) + b = b[8:] + } +} + +// permute applies the KeccakF-1600 permutation. It handles +// any input-output buffering. +func (d *state) permute() { + switch d.state { + case spongeAbsorbing: + // If we're absorbing, we need to xor the input into the state + // before applying the permutation. + d.xorIn(d.buf) + d.buf = d.storage[:0] + keccakF1600(&d.a) + case spongeSqueezing: + // If we're squeezing, we need to apply the permutatin before + // copying more output. + keccakF1600(&d.a) + d.buf = d.storage[:d.rate] + d.copyOut(d.buf) + } +} + +// pads appends the domain separation bits in dsbyte, applies +// the multi-bitrate 10..1 padding rule, and permutes the state. +func (d *state) padAndPermute(dsbyte byte) { + if d.buf == nil { + d.buf = d.storage[:0] + } + // Pad with this instance's domain-separator bits. We know that there's + // at least one byte of space in d.buf because, if it were full, + // permute would have been called to empty it. dsbyte also contains the + // first one bit for the padding. See the comment in the state struct. + d.buf = append(d.buf, dsbyte) + zerosStart := len(d.buf) + d.buf = d.storage[:d.rate] + for i := zerosStart; i < d.rate; i++ { + d.buf[i] = 0 + } + // This adds the final one bit for the padding. Because of the way that + // bits are numbered from the LSB upwards, the final bit is the MSB of + // the last byte. + d.buf[d.rate-1] ^= 0x80 + // Apply the permutation + d.permute() + d.state = spongeSqueezing + d.buf = d.storage[:d.rate] + d.copyOut(d.buf) +} + +// Write absorbs more data into the hash's state. It produces an error +// if more data is written to the ShakeHash after writing +func (d *state) Write(p []byte) (written int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: write to sponge after read") + } + if d.buf == nil { + d.buf = d.storage[:0] + } + written = len(p) + + for len(p) > 0 { + if len(d.buf) == 0 && len(p) >= d.rate { + // The fast path; absorb a full "rate" bytes of input and apply the permutation. + d.xorIn(p[:d.rate]) + p = p[d.rate:] + keccakF1600(&d.a) + } else { + // The slow path; buffer the input until we can fill the sponge, and then xor it in. + todo := d.rate - len(d.buf) + if todo > len(p) { + todo = len(p) + } + d.buf = append(d.buf, p[:todo]...) + p = p[todo:] + + // If the sponge is full, apply the permutation. + if len(d.buf) == d.rate { + d.permute() + } + } + } + + return +} + +// Read squeezes an arbitrary number of bytes from the sponge. +func (d *state) Read(out []byte) (n int, err error) { + // If we're still absorbing, pad and apply the permutation. + if d.state == spongeAbsorbing { + d.padAndPermute(d.dsbyte) + } + + n = len(out) + + // Now, do the squeezing. + for len(out) > 0 { + n := copy(out, d.buf) + d.buf = d.buf[n:] + out = out[n:] + + // Apply the permutation if we've squeezed the sponge dry. + if len(d.buf) == 0 { + d.permute() + } + } + + return +} + +// Sum applies padding to the hash state and then squeezes out the desired +// number of output bytes. +func (d *state) Sum(in []byte) []byte { + // Make a copy of the original hash so that caller can keep writing + // and summing. + dup := d.clone() + hash := make([]byte, dup.outputLen) + dup.Read(hash) + return append(in, hash...) +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3_test.go new file mode 100644 index 00000000..6f84863a --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/sha3_test.go @@ -0,0 +1,249 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// Tests include all the ShortMsgKATs provided by the Keccak team at +// https://github.com/gvanas/KeccakCodePackage +// +// They only include the zero-bit case of the utterly useless bitwise +// testvectors published by NIST in the draft of FIPS-202. + +import ( + "bytes" + "compress/flate" + "encoding/hex" + "encoding/json" + "hash" + "os" + "strings" + "testing" +) + +const ( + testString = "brekeccakkeccak koax koax" + katFilename = "keccakKats.json.deflate" +) + +// Internal-use instances of SHAKE used to test against KATs. +func newHashShake128() hash.Hash { + return &state{rate: 168, dsbyte: 0x1f, outputLen: 512} +} +func newHashShake256() hash.Hash { + return &state{rate: 136, dsbyte: 0x1f, outputLen: 512} +} + +// testDigests contains functions returning hash.Hash instances +// with output-length equal to the KAT length for both SHA-3 and +// SHAKE instances. +var testDigests = map[string]func() hash.Hash{ + "SHA3-224": New224, + "SHA3-256": New256, + "SHA3-384": New384, + "SHA3-512": New512, + "SHAKE128": newHashShake128, + "SHAKE256": newHashShake256, +} + +// testShakes contains functions returning ShakeHash instances for +// testing the ShakeHash-specific interface. +var testShakes = map[string]func() ShakeHash{ + "SHAKE128": NewShake128, + "SHAKE256": NewShake256, +} + +// decodeHex converts an hex-encoded string into a raw byte string. +func decodeHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +// structs used to marshal JSON test-cases. +type KeccakKats struct { + Kats map[string][]struct { + Digest string `json:"digest"` + Length int64 `json:"length"` + Message string `json:"message"` + } +} + +// TestKeccakKats tests the SHA-3 and Shake implementations against all the +// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage +// (The testvectors are stored in keccakKats.json.deflate due to their length.) +func TestKeccakKats(t *testing.T) { + // Read the KATs. + deflated, err := os.Open(katFilename) + if err != nil { + t.Errorf("Error opening %s: %s", katFilename, err) + } + file := flate.NewReader(deflated) + dec := json.NewDecoder(file) + var katSet KeccakKats + err = dec.Decode(&katSet) + if err != nil { + t.Errorf("%s", err) + } + + // Do the KATs. + for functionName, kats := range katSet.Kats { + d := testDigests[functionName]() + t.Logf("%s", functionName) + for _, kat := range kats { + d.Reset() + in, err := hex.DecodeString(kat.Message) + if err != nil { + t.Errorf("%s", err) + } + d.Write(in[:kat.Length/8]) + got := strings.ToUpper(hex.EncodeToString(d.Sum(nil))) + want := kat.Digest + if got != want { + t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s", + functionName, kat.Length, kat.Message, got, want) + t.Logf("wanted %+v", kat) + t.FailNow() + } + } + } +} + +// TestUnalignedWrite tests that writing data in an arbitrary pattern with +// small input buffers. +func TestUnalignedWrite(t *testing.T) { + buf := sequentialBytes(0x10000) + for alg, df := range testDigests { + d := df() + d.Reset() + d.Write(buf) + want := d.Sum(nil) + d.Reset() + for i := 0; i < len(buf); { + // Cycle through offsets which make a 137 byte sequence. + // Because 137 is prime this sequence should exercise all corner cases. + offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} + for _, j := range offsets { + if v := len(buf) - i; v < j { + j = v + } + d.Write(buf[i : i+j]) + i += j + } + } + got := d.Sum(nil) + if !bytes.Equal(got, want) { + t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) + } + } +} + +// Test that appending works when reallocation is necessary. +func TestAppend(t *testing.T) { + d := New224() + + for capacity := 2; capacity < 64; capacity += 64 { + // The first time around the loop, Sum will have to reallocate. + // The second time, it will not. + buf := make([]byte, 2, capacity) + d.Reset() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("got %s, want %s", got, expected) + } + } +} + +// Test that appending works when no reallocation is necessary. +func TestAppendNoRealloc(t *testing.T) { + buf := make([]byte, 1, 200) + d := New224() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("got %s, want %s", got, expected) + } +} + +// TestSqueezing checks that squeezing the full output a single time produces +// the same output as repeatedly squeezing the instance. +func TestSqueezing(t *testing.T) { + for functionName, newShakeHash := range testShakes { + t.Logf("%s", functionName) + d0 := newShakeHash() + d0.Write([]byte(testString)) + ref := make([]byte, 32) + d0.Read(ref) + + d1 := newShakeHash() + d1.Write([]byte(testString)) + var multiple []byte + for _ = range ref { + one := make([]byte, 1) + d1.Read(one) + multiple = append(multiple, one...) + } + if !bytes.Equal(ref, multiple) { + t.Errorf("squeezing %d bytes one at a time failed", len(ref)) + } + } +} + +func TestReadSimulation(t *testing.T) { + d := NewShake256() + d.Write(nil) + dwr := make([]byte, 32) + d.Read(dwr) + +} + +// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. +func sequentialBytes(size int) []byte { + result := make([]byte, size) + for i := range result { + result[i] = byte(i) + } + return result +} + +// BenchmarkPermutationFunction measures the speed of the permutation function +// with no input data. +func BenchmarkPermutationFunction(b *testing.B) { + b.SetBytes(int64(200)) + var lanes [25]uint64 + for i := 0; i < b.N; i++ { + keccakF1600(&lanes) + } +} + +// benchmarkBulkHash tests the speed to hash a buffer of buflen. +func benchmarkBulkHash(b *testing.B, h hash.Hash, size int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + b.SetBytes(int64(size)) + b.StartTimer() + + var state []byte + for i := 0; i < b.N; i++ { + h.Write(data) + state = h.Sum(state[:0]) + } + b.StopTimer() + h.Reset() +} + +func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkBulkHash(b, New512(), 1350) } +func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkBulkHash(b, New384(), 1350) } +func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkBulkHash(b, New256(), 1350) } +func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkBulkHash(b, New224(), 1350) } +func BenchmarkShake256_MTU(b *testing.B) { benchmarkBulkHash(b, newHashShake256(), 1350) } +func BenchmarkShake128_MTU(b *testing.B) { benchmarkBulkHash(b, newHashShake128(), 1350) } + +func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkBulkHash(b, New512(), 1<<20) } +func BenchmarkShake256_1MiB(b *testing.B) { benchmarkBulkHash(b, newHashShake256(), 1<<20) } diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/shake.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/shake.go new file mode 100644 index 00000000..841f9860 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/sha3/shake.go @@ -0,0 +1,60 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This file defines the ShakeHash interface, and provides +// functions for creating SHAKE instances, as well as utility +// functions for hashing bytes to arbitrary-length output. + +import ( + "io" +) + +// ShakeHash defines the interface to hash functions that +// support arbitrary-length output. +type ShakeHash interface { + // Write absorbs more data into the hash's state. It panics if input is + // written to it after output has been read from it. + io.Writer + + // Read reads more output from the hash; reading affects the hash's + // state. (ShakeHash.Read is thus very different from Hash.Sum) + // It never returns an error. + io.Reader + + // Clone returns a copy of the ShakeHash in its current state. + Clone() ShakeHash + + // Reset resets the ShakeHash to its initial state. + Reset() +} + +func (d *state) Clone() ShakeHash { + return d.clone() +} + +// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 128 bits against all attacks if at +// least 32 bytes of its output are used. +func NewShake128() ShakeHash { return &state{rate: 168, dsbyte: 0x1f} } + +// NewShake256 creates a new SHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 256 bits against all attacks if +// at least 64 bytes of its output are used. +func NewShake256() ShakeHash { return &state{rate: 136, dsbyte: 0x1f} } + +// ShakeSum128 writes an arbitrary-length digest of data into hash. +func ShakeSum128(hash, data []byte) { + h := NewShake128() + h.Write(data) + h.Read(hash) +} + +// ShakeSum256 writes an arbitrary-length digest of data into hash. +func ShakeSum256(hash, data []byte) { + h := NewShake256() + h.Write(data) + h.Read(hash) +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client.go new file mode 100644 index 00000000..1a916961 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client.go @@ -0,0 +1,563 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + Package agent implements a client to an ssh-agent daemon. + +References: + [PROTOCOL.agent]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent +*/ +package agent // import "golang.org/x/crypto/ssh/agent" + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "sync" + + "golang.org/x/crypto/ssh" +) + +// Agent represents the capabilities of an ssh-agent. +type Agent interface { + // List returns the identities known to the agent. + List() ([]*Key, error) + + // Sign has the agent sign the data using a protocol 2 key as defined + // in [PROTOCOL.agent] section 2.6.2. + Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) + + // Insert adds a private key to the agent. If a certificate + // is given, that certificate is added as public key. + Add(s interface{}, cert *ssh.Certificate, comment string) error + + // Remove removes all identities with the given public key. + Remove(key ssh.PublicKey) error + + // RemoveAll removes all identities. + RemoveAll() error + + // Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. + Lock(passphrase []byte) error + + // Unlock undoes the effect of Lock + Unlock(passphrase []byte) error + + // Signers returns signers for all the known keys. + Signers() ([]ssh.Signer, error) +} + +// See [PROTOCOL.agent], section 3. +const ( + agentRequestV1Identities = 1 + + // 3.2 Requests from client to agent for protocol 2 key operations + agentAddIdentity = 17 + agentRemoveIdentity = 18 + agentRemoveAllIdentities = 19 + agentAddIdConstrained = 25 + + // 3.3 Key-type independent requests from client to agent + agentAddSmartcardKey = 20 + agentRemoveSmartcardKey = 21 + agentLock = 22 + agentUnlock = 23 + agentAddSmartcardKeyConstrained = 26 + + // 3.7 Key constraint identifiers + agentConstrainLifetime = 1 + agentConstrainConfirm = 2 +) + +// maxAgentResponseBytes is the maximum agent reply size that is accepted. This +// is a sanity check, not a limit in the spec. +const maxAgentResponseBytes = 16 << 20 + +// Agent messages: +// These structures mirror the wire format of the corresponding ssh agent +// messages found in [PROTOCOL.agent]. + +// 3.4 Generic replies from agent to client +const agentFailure = 5 + +type failureAgentMsg struct{} + +const agentSuccess = 6 + +type successAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentRequestIdentities = 11 + +type requestIdentitiesAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentIdentitiesAnswer = 12 + +type identitiesAnswerAgentMsg struct { + NumKeys uint32 `sshtype:"12"` + Keys []byte `ssh:"rest"` +} + +// See [PROTOCOL.agent], section 2.6.2. +const agentSignRequest = 13 + +type signRequestAgentMsg struct { + KeyBlob []byte `sshtype:"13"` + Data []byte + Flags uint32 +} + +// See [PROTOCOL.agent], section 2.6.2. + +// 3.6 Replies from agent to client for protocol 2 key operations +const agentSignResponse = 14 + +type signResponseAgentMsg struct { + SigBlob []byte `sshtype:"14"` +} + +type publicKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +// Key represents a protocol 2 public key as defined in +// [PROTOCOL.agent], section 2.5.2. +type Key struct { + Format string + Blob []byte + Comment string +} + +func clientErr(err error) error { + return fmt.Errorf("agent: client error: %v", err) +} + +// String returns the storage form of an agent key with the format, base64 +// encoded serialized key, and the comment if it is not empty. +func (k *Key) String() string { + s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) + + if k.Comment != "" { + s += " " + k.Comment + } + + return s +} + +// Type returns the public key type. +func (k *Key) Type() string { + return k.Format +} + +// Marshal returns key blob to satisfy the ssh.PublicKey interface. +func (k *Key) Marshal() []byte { + return k.Blob +} + +// Verify satisfies the ssh.PublicKey interface, but is not +// implemented for agent keys. +func (k *Key) Verify(data []byte, sig *ssh.Signature) error { + return errors.New("agent: agent key does not know how to verify") +} + +type wireKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +func parseKey(in []byte) (out *Key, rest []byte, err error) { + var record struct { + Blob []byte + Comment string + Rest []byte `ssh:"rest"` + } + + if err := ssh.Unmarshal(in, &record); err != nil { + return nil, nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(record.Blob, &wk); err != nil { + return nil, nil, err + } + + return &Key{ + Format: wk.Format, + Blob: record.Blob, + Comment: record.Comment, + }, record.Rest, nil +} + +// client is a client for an ssh-agent process. +type client struct { + // conn is typically a *net.UnixConn + conn io.ReadWriter + // mu is used to prevent concurrent access to the agent + mu sync.Mutex +} + +// NewClient returns an Agent that talks to an ssh-agent process over +// the given connection. +func NewClient(rw io.ReadWriter) Agent { + return &client{conn: rw} +} + +// call sends an RPC to the agent. On success, the reply is +// unmarshaled into reply and replyType is set to the first byte of +// the reply, which contains the type of the message. +func (c *client) call(req []byte) (reply interface{}, err error) { + c.mu.Lock() + defer c.mu.Unlock() + + msg := make([]byte, 4+len(req)) + binary.BigEndian.PutUint32(msg, uint32(len(req))) + copy(msg[4:], req) + if _, err = c.conn.Write(msg); err != nil { + return nil, clientErr(err) + } + + var respSizeBuf [4]byte + if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { + return nil, clientErr(err) + } + respSize := binary.BigEndian.Uint32(respSizeBuf[:]) + if respSize > maxAgentResponseBytes { + return nil, clientErr(err) + } + + buf := make([]byte, respSize) + if _, err = io.ReadFull(c.conn, buf); err != nil { + return nil, clientErr(err) + } + reply, err = unmarshal(buf) + if err != nil { + return nil, clientErr(err) + } + return reply, err +} + +func (c *client) simpleCall(req []byte) error { + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +func (c *client) RemoveAll() error { + return c.simpleCall([]byte{agentRemoveAllIdentities}) +} + +func (c *client) Remove(key ssh.PublicKey) error { + req := ssh.Marshal(&agentRemoveIdentityMsg{ + KeyBlob: key.Marshal(), + }) + return c.simpleCall(req) +} + +func (c *client) Lock(passphrase []byte) error { + req := ssh.Marshal(&agentLockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +func (c *client) Unlock(passphrase []byte) error { + req := ssh.Marshal(&agentUnlockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +// List returns the identities known to the agent. +func (c *client) List() ([]*Key, error) { + // see [PROTOCOL.agent] section 2.5.2. + req := []byte{agentRequestIdentities} + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *identitiesAnswerAgentMsg: + if msg.NumKeys > maxAgentResponseBytes/8 { + return nil, errors.New("agent: too many keys in agent reply") + } + keys := make([]*Key, msg.NumKeys) + data := msg.Keys + for i := uint32(0); i < msg.NumKeys; i++ { + var key *Key + var err error + if key, data, err = parseKey(data); err != nil { + return nil, err + } + keys[i] = key + } + return keys, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to list keys") + } + panic("unreachable") +} + +// Sign has the agent sign the data using a protocol 2 key as defined +// in [PROTOCOL.agent] section 2.6.2. +func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + req := ssh.Marshal(signRequestAgentMsg{ + KeyBlob: key.Marshal(), + Data: data, + }) + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *signResponseAgentMsg: + var sig ssh.Signature + if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { + return nil, err + } + + return &sig, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to sign challenge") + } + panic("unreachable") +} + +// unmarshal parses an agent message in packet, returning the parsed +// form and the message type of packet. +func unmarshal(packet []byte) (interface{}, error) { + if len(packet) < 1 { + return nil, errors.New("agent: empty packet") + } + var msg interface{} + switch packet[0] { + case agentFailure: + return new(failureAgentMsg), nil + case agentSuccess: + return new(successAgentMsg), nil + case agentIdentitiesAnswer: + msg = new(identitiesAnswerAgentMsg) + case agentSignResponse: + msg = new(signResponseAgentMsg) + default: + return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) + } + if err := ssh.Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} + +type rsaKeyMsg struct { + Type string `sshtype:"17"` + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string +} + +type dsaKeyMsg struct { + Type string `sshtype:"17"` + P *big.Int + Q *big.Int + G *big.Int + Y *big.Int + X *big.Int + Comments string +} + +type ecdsaKeyMsg struct { + Type string `sshtype:"17"` + Curve string + KeyBytes []byte + D *big.Int + Comments string +} + +// Insert adds a private key to the agent. +func (c *client) insertKey(s interface{}, comment string) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaKeyMsg{ + Type: ssh.KeyAlgoRSA, + N: k.N, + E: big.NewInt(int64(k.E)), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaKeyMsg{ + Type: ssh.KeyAlgoDSA, + P: k.P, + Q: k.Q, + G: k.G, + Y: k.Y, + X: k.X, + Comments: comment, + }) + case *ecdsa.PrivateKey: + nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) + req = ssh.Marshal(ecdsaKeyMsg{ + Type: "ecdsa-sha2-" + nistID, + Curve: nistID, + KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y), + D: k.D, + Comments: comment, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +type rsaCertMsg struct { + Type string `sshtype:"17"` + CertBytes []byte + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string +} + +type dsaCertMsg struct { + Type string `sshtype:"17"` + CertBytes []byte + X *big.Int + Comments string +} + +type ecdsaCertMsg struct { + Type string `sshtype:"17"` + CertBytes []byte + D *big.Int + Comments string +} + +// Insert adds a private key to the agent. If a certificate is given, +// that certificate is added instead as public key. +func (c *client) Add(s interface{}, cert *ssh.Certificate, comment string) error { + if cert == nil { + return c.insertKey(s, comment) + } else { + return c.insertCert(s, cert, comment) + } +} + +func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + X: k.X, + Comments: comment, + }) + case *ecdsa.PrivateKey: + req = ssh.Marshal(ecdsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Comments: comment, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + + signer, err := ssh.NewSignerFromKey(s) + if err != nil { + return err + } + if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { + return errors.New("agent: signer and cert have different public key") + } + + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +// Signers provides a callback for client authentication. +func (c *client) Signers() ([]ssh.Signer, error) { + keys, err := c.List() + if err != nil { + return nil, err + } + + var result []ssh.Signer + for _, k := range keys { + result = append(result, &agentKeyringSigner{c, k}) + } + return result, nil +} + +type agentKeyringSigner struct { + agent *client + pub ssh.PublicKey +} + +func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { + return s.pub +} + +func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + // The agent has its own entropy source, so the rand argument is ignored. + return s.agent.Sign(s.pub, data) +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client_test.go new file mode 100644 index 00000000..80e2c2c3 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/client_test.go @@ -0,0 +1,278 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "bytes" + "crypto/rand" + "errors" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "testing" + + "golang.org/x/crypto/ssh" +) + +// startAgent executes ssh-agent, and returns a Agent interface to it. +func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) { + if testing.Short() { + // ssh-agent is not always available, and the key + // types supported vary by platform. + t.Skip("skipping test due to -short") + } + + bin, err := exec.LookPath("ssh-agent") + if err != nil { + t.Skip("could not find ssh-agent") + } + + cmd := exec.Command(bin, "-s") + out, err := cmd.Output() + if err != nil { + t.Fatalf("cmd.Output: %v", err) + } + + /* Output looks like: + + SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK; + SSH_AGENT_PID=15542; export SSH_AGENT_PID; + echo Agent pid 15542; + */ + fields := bytes.Split(out, []byte(";")) + line := bytes.SplitN(fields[0], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AUTH_SOCK" { + t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0]) + } + socket = string(line[1]) + + line = bytes.SplitN(fields[2], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AGENT_PID" { + t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2]) + } + pidStr := line[1] + pid, err := strconv.Atoi(string(pidStr)) + if err != nil { + t.Fatalf("Atoi(%q): %v", pidStr, err) + } + + conn, err := net.Dial("unix", string(socket)) + if err != nil { + t.Fatalf("net.Dial: %v", err) + } + + ac := NewClient(conn) + return ac, socket, func() { + proc, _ := os.FindProcess(pid) + if proc != nil { + proc.Kill() + } + conn.Close() + os.RemoveAll(filepath.Dir(socket)) + } +} + +func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate) { + agent, _, cleanup := startAgent(t) + defer cleanup() + + testAgentInterface(t, agent, key, cert) +} + +func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate) { + signer, err := ssh.NewSignerFromKey(key) + if err != nil { + t.Fatalf("NewSignerFromKey(%T): %v", key, err) + } + // The agent should start up empty. + if keys, err := agent.List(); err != nil { + t.Fatalf("RequestIdentities: %v", err) + } else if len(keys) > 0 { + t.Fatalf("got %d keys, want 0: %v", len(keys), keys) + } + + // Attempt to insert the key, with certificate if specified. + var pubKey ssh.PublicKey + if cert != nil { + err = agent.Add(key, cert, "comment") + pubKey = cert + } else { + err = agent.Add(key, nil, "comment") + pubKey = signer.PublicKey() + } + if err != nil { + t.Fatalf("insert(%T): %v", key, err) + } + + // Did the key get inserted successfully? + if keys, err := agent.List(); err != nil { + t.Fatalf("List: %v", err) + } else if len(keys) != 1 { + t.Fatalf("got %v, want 1 key", keys) + } else if keys[0].Comment != "comment" { + t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment") + } else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) { + t.Fatalf("key mismatch") + } + + // Can the agent make a valid signature? + data := []byte("hello") + sig, err := agent.Sign(pubKey, data) + if err != nil { + t.Fatalf("Sign(%s): %v", pubKey.Type(), err) + } + + if err := pubKey.Verify(data, sig); err != nil { + t.Fatalf("Verify(%s): %v", pubKey.Type(), err) + } +} + +func TestAgent(t *testing.T) { + for _, keyType := range []string{"rsa", "dsa", "ecdsa"} { + testAgent(t, testPrivateKeys[keyType], nil) + } +} + +func TestCert(t *testing.T) { + cert := &ssh.Certificate{ + Key: testPublicKeys["rsa"], + ValidBefore: ssh.CertTimeInfinity, + CertType: ssh.UserCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + testAgent(t, testPrivateKeys["rsa"], cert) +} + +// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and +// therefore is buffered (net.Pipe deadlocks if both sides start with +// a write.) +func netPipe() (net.Conn, net.Conn, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, nil, err + } + defer listener.Close() + c1, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + return nil, nil, err + } + + c2, err := listener.Accept() + if err != nil { + c1.Close() + return nil, nil, err + } + + return c1, c2, nil +} + +func TestAuth(t *testing.T) { + a, b, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + defer a.Close() + defer b.Close() + + agent, _, cleanup := startAgent(t) + defer cleanup() + + if err := agent.Add(testPrivateKeys["rsa"], nil, "comment"); err != nil { + t.Errorf("Add: %v", err) + } + + serverConf := ssh.ServerConfig{} + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { + return nil, nil + } + + return nil, errors.New("pubkey rejected") + } + + go func() { + conn, _, _, err := ssh.NewServerConn(a, &serverConf) + if err != nil { + t.Fatalf("Server: %v", err) + } + conn.Close() + }() + + conf := ssh.ClientConfig{} + conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers)) + conn, _, _, err := ssh.NewClientConn(b, "", &conf) + if err != nil { + t.Fatalf("NewClientConn: %v", err) + } + conn.Close() +} + +func TestLockClient(t *testing.T) { + agent, _, cleanup := startAgent(t) + defer cleanup() + testLockAgent(agent, t) +} + +func testLockAgent(agent Agent, t *testing.T) { + if err := agent.Add(testPrivateKeys["rsa"], nil, "comment 1"); err != nil { + t.Errorf("Add: %v", err) + } + if err := agent.Add(testPrivateKeys["dsa"], nil, "comment dsa"); err != nil { + t.Errorf("Add: %v", err) + } + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 2 { + t.Errorf("Want 2 keys, got %v", keys) + } + + passphrase := []byte("secret") + if err := agent.Lock(passphrase); err != nil { + t.Errorf("Lock: %v", err) + } + + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 0 { + t.Errorf("Want 0 keys, got %v", keys) + } + + signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"]) + if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil { + t.Fatalf("Sign did not fail") + } + + if err := agent.Remove(signer.PublicKey()); err == nil { + t.Fatalf("Remove did not fail") + } + + if err := agent.RemoveAll(); err == nil { + t.Fatalf("RemoveAll did not fail") + } + + if err := agent.Unlock(nil); err == nil { + t.Errorf("Unlock with wrong passphrase succeeded") + } + if err := agent.Unlock(passphrase); err != nil { + t.Errorf("Unlock: %v", err) + } + + if err := agent.Remove(signer.PublicKey()); err != nil { + t.Fatalf("Remove: %v", err) + } + + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 1 { + t.Errorf("Want 1 keys, got %v", keys) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/forward.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/forward.go new file mode 100644 index 00000000..fd24ba90 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/forward.go @@ -0,0 +1,103 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "errors" + "io" + "net" + "sync" + + "golang.org/x/crypto/ssh" +) + +// RequestAgentForwarding sets up agent forwarding for the session. +// ForwardToAgent or ForwardToRemote should be called to route +// the authentication requests. +func RequestAgentForwarding(session *ssh.Session) error { + ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil) + if err != nil { + return err + } + if !ok { + return errors.New("forwarding request denied") + } + return nil +} + +// ForwardToAgent routes authentication requests to the given keyring. +func ForwardToAgent(client *ssh.Client, keyring Agent) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go func() { + ServeAgent(keyring, channel) + channel.Close() + }() + } + }() + return nil +} + +const channelType = "auth-agent@openssh.com" + +// ForwardToRemote routes authentication requests to the ssh-agent +// process serving on the given unix socket. +func ForwardToRemote(client *ssh.Client, addr string) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + conn, err := net.Dial("unix", addr) + if err != nil { + return err + } + conn.Close() + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go forwardUnixSocket(channel, addr) + } + }() + return nil +} + +func forwardUnixSocket(channel ssh.Channel, addr string) { + conn, err := net.Dial("unix", addr) + if err != nil { + return + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + io.Copy(conn, channel) + conn.(*net.UnixConn).CloseWrite() + wg.Done() + }() + go func() { + io.Copy(channel, conn) + channel.CloseWrite() + wg.Done() + }() + + wg.Wait() + conn.Close() + channel.Close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/keyring.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/keyring.go new file mode 100644 index 00000000..831a5b9a --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/keyring.go @@ -0,0 +1,183 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "bytes" + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "sync" + + "golang.org/x/crypto/ssh" +) + +type privKey struct { + signer ssh.Signer + comment string +} + +type keyring struct { + mu sync.Mutex + keys []privKey + + locked bool + passphrase []byte +} + +var errLocked = errors.New("agent: locked") + +// NewKeyring returns an Agent that holds keys in memory. It is safe +// for concurrent use by multiple goroutines. +func NewKeyring() Agent { + return &keyring{} +} + +// RemoveAll removes all identities. +func (r *keyring) RemoveAll() error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.keys = nil + return nil +} + +// Remove removes all identities with the given public key. +func (r *keyring) Remove(key ssh.PublicKey) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + want := key.Marshal() + found := false + for i := 0; i < len(r.keys); { + if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) { + found = true + r.keys[i] = r.keys[len(r.keys)-1] + r.keys = r.keys[len(r.keys)-1:] + continue + } else { + i++ + } + } + + if !found { + return errors.New("agent: key not found") + } + return nil +} + +// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. +func (r *keyring) Lock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.locked = true + r.passphrase = passphrase + return nil +} + +// Unlock undoes the effect of Lock +func (r *keyring) Unlock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if !r.locked { + return errors.New("agent: not locked") + } + if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) { + return fmt.Errorf("agent: incorrect passphrase") + } + + r.locked = false + r.passphrase = nil + return nil +} + +// List returns the identities known to the agent. +func (r *keyring) List() ([]*Key, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + // section 2.7: locked agents return empty. + return nil, nil + } + + var ids []*Key + for _, k := range r.keys { + pub := k.signer.PublicKey() + ids = append(ids, &Key{ + Format: pub.Type(), + Blob: pub.Marshal(), + Comment: k.comment}) + } + return ids, nil +} + +// Insert adds a private key to the keyring. If a certificate +// is given, that certificate is added as public key. +func (r *keyring) Add(priv interface{}, cert *ssh.Certificate, comment string) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + signer, err := ssh.NewSignerFromKey(priv) + + if err != nil { + return err + } + + if cert != nil { + signer, err = ssh.NewCertSigner(cert, signer) + if err != nil { + return err + } + } + + r.keys = append(r.keys, privKey{signer, comment}) + + return nil +} + +// Sign returns a signature for the data. +func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + wanted := key.Marshal() + for _, k := range r.keys { + if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) { + return k.signer.Sign(rand.Reader, data) + } + } + return nil, errors.New("not found") +} + +// Signers returns signers for all the known keys. +func (r *keyring) Signers() ([]ssh.Signer, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + s := make([]ssh.Signer, len(r.keys)) + for _, k := range r.keys { + s = append(s, k.signer) + } + return s, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server.go new file mode 100644 index 00000000..be9df0eb --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server.go @@ -0,0 +1,209 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "crypto/rsa" + "encoding/binary" + "fmt" + "io" + "log" + "math/big" + + "golang.org/x/crypto/ssh" +) + +// Server wraps an Agent and uses it to implement the agent side of +// the SSH-agent, wire protocol. +type server struct { + agent Agent +} + +func (s *server) processRequestBytes(reqData []byte) []byte { + rep, err := s.processRequest(reqData) + if err != nil { + if err != errLocked { + // TODO(hanwen): provide better logging interface? + log.Printf("agent %d: %v", reqData[0], err) + } + return []byte{agentFailure} + } + + if err == nil && rep == nil { + return []byte{agentSuccess} + } + + return ssh.Marshal(rep) +} + +func marshalKey(k *Key) []byte { + var record struct { + Blob []byte + Comment string + } + record.Blob = k.Marshal() + record.Comment = k.Comment + + return ssh.Marshal(&record) +} + +type agentV1IdentityMsg struct { + Numkeys uint32 `sshtype:"2"` +} + +type agentRemoveIdentityMsg struct { + KeyBlob []byte `sshtype:"18"` +} + +type agentLockMsg struct { + Passphrase []byte `sshtype:"22"` +} + +type agentUnlockMsg struct { + Passphrase []byte `sshtype:"23"` +} + +func (s *server) processRequest(data []byte) (interface{}, error) { + switch data[0] { + case agentRequestV1Identities: + return &agentV1IdentityMsg{0}, nil + case agentRemoveIdentity: + var req agentRemoveIdentityMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob}) + + case agentRemoveAllIdentities: + return nil, s.agent.RemoveAll() + + case agentLock: + var req agentLockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + return nil, s.agent.Lock(req.Passphrase) + + case agentUnlock: + var req agentLockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + return nil, s.agent.Unlock(req.Passphrase) + + case agentSignRequest: + var req signRequestAgentMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + k := &Key{ + Format: wk.Format, + Blob: req.KeyBlob, + } + + sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags. + if err != nil { + return nil, err + } + return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil + case agentRequestIdentities: + keys, err := s.agent.List() + if err != nil { + return nil, err + } + + rep := identitiesAnswerAgentMsg{ + NumKeys: uint32(len(keys)), + } + for _, k := range keys { + rep.Keys = append(rep.Keys, marshalKey(k)...) + } + return rep, nil + case agentAddIdentity: + return nil, s.insertIdentity(data) + } + + return nil, fmt.Errorf("unknown opcode %d", data[0]) +} + +func (s *server) insertIdentity(req []byte) error { + var record struct { + Type string `sshtype:"17"` + Rest []byte `ssh:"rest"` + } + if err := ssh.Unmarshal(req, &record); err != nil { + return err + } + + switch record.Type { + case ssh.KeyAlgoRSA: + var k rsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return err + } + + priv := rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + E: int(k.E.Int64()), + N: k.N, + }, + D: k.D, + Primes: []*big.Int{k.P, k.Q}, + } + priv.Precompute() + + return s.agent.Add(&priv, nil, k.Comments) + } + return fmt.Errorf("not implemented: %s", record.Type) +} + +// ServeAgent serves the agent protocol on the given connection. It +// returns when an I/O error occurs. +func ServeAgent(agent Agent, c io.ReadWriter) error { + s := &server{agent} + + var length [4]byte + for { + if _, err := io.ReadFull(c, length[:]); err != nil { + return err + } + l := binary.BigEndian.Uint32(length[:]) + if l > maxAgentResponseBytes { + // We also cap requests. + return fmt.Errorf("agent: request too large: %d", l) + } + + req := make([]byte, l) + if _, err := io.ReadFull(c, req); err != nil { + return err + } + + repData := s.processRequestBytes(req) + if len(repData) > maxAgentResponseBytes { + return fmt.Errorf("agent: reply too large: %d bytes", len(repData)) + } + + binary.BigEndian.PutUint32(length[:], uint32(len(repData))) + if _, err := c.Write(length[:]); err != nil { + return err + } + if _, err := c.Write(repData); err != nil { + return err + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server_test.go new file mode 100644 index 00000000..def5f8cc --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/server_test.go @@ -0,0 +1,77 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestServer(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + client := NewClient(c1) + + go ServeAgent(NewKeyring(), c2) + + testAgentInterface(t, client, testPrivateKeys["rsa"], nil) +} + +func TestLockServer(t *testing.T) { + testLockAgent(NewKeyring(), t) +} + +func TestSetupForwardAgent(t *testing.T) { + a, b, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + defer a.Close() + defer b.Close() + + _, socket, cleanup := startAgent(t) + defer cleanup() + + serverConf := ssh.ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["rsa"]) + incoming := make(chan *ssh.ServerConn, 1) + go func() { + conn, _, _, err := ssh.NewServerConn(a, &serverConf) + if err != nil { + t.Fatalf("Server: %v", err) + } + incoming <- conn + }() + + conf := ssh.ClientConfig{} + conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf) + if err != nil { + t.Fatalf("NewClientConn: %v", err) + } + client := ssh.NewClient(conn, chans, reqs) + + if err := ForwardToRemote(client, socket); err != nil { + t.Fatalf("SetupForwardAgent: %v", err) + } + + server := <-incoming + ch, reqs, err := server.OpenChannel(channelType, nil) + if err != nil { + t.Fatalf("OpenChannel(%q): %v", channelType, err) + } + go ssh.DiscardRequests(reqs) + + agentClient := NewClient(ch) + testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil) + conn.Close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/testdata_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/testdata_test.go new file mode 100644 index 00000000..b7a8781e --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/agent/testdata_test.go @@ -0,0 +1,64 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package agent + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]ssh.Signer + testPublicKeys map[string]ssh.PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]ssh.Signer, n) + testPublicKeys = make(map[string]ssh.PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &ssh.Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/benchmark_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/benchmark_test.go new file mode 100644 index 00000000..d9f7eb9b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/benchmark_test.go @@ -0,0 +1,122 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "io" + "net" + "testing" +) + +type server struct { + *ServerConn + chans <-chan NewChannel +} + +func newServer(c net.Conn, conf *ServerConfig) (*server, error) { + sconn, chans, reqs, err := NewServerConn(c, conf) + if err != nil { + return nil, err + } + go DiscardRequests(reqs) + return &server{sconn, chans}, nil +} + +func (s *server) Accept() (NewChannel, error) { + n, ok := <-s.chans + if !ok { + return nil, io.EOF + } + return n, nil +} + +func sshPipe() (Conn, *server, error) { + c1, c2, err := netPipe() + if err != nil { + return nil, nil, err + } + + clientConf := ClientConfig{ + User: "user", + } + serverConf := ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["ecdsa"]) + done := make(chan *server, 1) + go func() { + server, err := newServer(c2, &serverConf) + if err != nil { + done <- nil + } + done <- server + }() + + client, _, reqs, err := NewClientConn(c1, "", &clientConf) + if err != nil { + return nil, nil, err + } + + server := <-done + if server == nil { + return nil, nil, errors.New("server handshake failed.") + } + go DiscardRequests(reqs) + + return client, server, nil +} + +func BenchmarkEndToEnd(b *testing.B) { + b.StopTimer() + + client, server, err := sshPipe() + if err != nil { + b.Fatalf("sshPipe: %v", err) + } + + defer client.Close() + defer server.Close() + + size := (1 << 20) + input := make([]byte, size) + output := make([]byte, size) + b.SetBytes(int64(size)) + done := make(chan int, 1) + + go func() { + newCh, err := server.Accept() + if err != nil { + b.Fatalf("Client: %v", err) + } + ch, incoming, err := newCh.Accept() + go DiscardRequests(incoming) + for i := 0; i < b.N; i++ { + if _, err := io.ReadFull(ch, output); err != nil { + b.Fatalf("ReadFull: %v", err) + } + } + ch.Close() + done <- 1 + }() + + ch, in, err := client.OpenChannel("speed", nil) + if err != nil { + b.Fatalf("OpenChannel: %v", err) + } + go DiscardRequests(in) + + b.ResetTimer() + b.StartTimer() + for i := 0; i < b.N; i++ { + if _, err := ch.Write(input); err != nil { + b.Fatalf("WriteFull: %v", err) + } + } + ch.Close() + b.StopTimer() + + <-done +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer.go similarity index 91% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer.go index 601dad34..6931b511 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer.go @@ -43,29 +43,29 @@ func newBuffer() *buffer { // buf must not be modified after the call to write. func (b *buffer) write(buf []byte) { b.Cond.L.Lock() - defer b.Cond.L.Unlock() e := &element{buf: buf} b.tail.next = e b.tail = e b.Cond.Signal() + b.Cond.L.Unlock() } // eof closes the buffer. Reads from the buffer once all // the data has been consumed will receive os.EOF. func (b *buffer) eof() error { b.Cond.L.Lock() - defer b.Cond.L.Unlock() b.closed = true b.Cond.Signal() + b.Cond.L.Unlock() return nil } -// Read reads data from the internal buffer in buf. -// Reads will block if no data is available, or until -// the buffer is closed. +// Read reads data from the internal buffer in buf. Reads will block +// if no data is available, or until the buffer is closed. func (b *buffer) Read(buf []byte) (n int, err error) { b.Cond.L.Lock() defer b.Cond.L.Unlock() + for len(buf) > 0 { // if there is data in b.head, copy it if len(b.head.buf) > 0 { @@ -79,10 +79,12 @@ func (b *buffer) Read(buf []byte) (n int, err error) { b.head = b.head.next continue } + // if at least one byte has been copied, return if n > 0 { break } + // if nothing was read, and there is nothing outstanding // check to see if the buffer is closed. if b.closed { diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer_test.go similarity index 89% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer_test.go index 135c4aec..d5781cb3 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/buffer_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/buffer_test.go @@ -9,33 +9,33 @@ import ( "testing" ) -var BYTES = []byte("abcdefghijklmnopqrstuvwxyz") +var alphabet = []byte("abcdefghijklmnopqrstuvwxyz") func TestBufferReadwrite(t *testing.T) { b := newBuffer() - b.write(BYTES[:10]) + b.write(alphabet[:10]) r, _ := b.Read(make([]byte, 10)) if r != 10 { t.Fatalf("Expected written == read == 10, written: 10, read %d", r) } b = newBuffer() - b.write(BYTES[:5]) + b.write(alphabet[:5]) r, _ = b.Read(make([]byte, 10)) if r != 5 { t.Fatalf("Expected written == read == 5, written: 5, read %d", r) } b = newBuffer() - b.write(BYTES[:10]) + b.write(alphabet[:10]) r, _ = b.Read(make([]byte, 5)) if r != 5 { t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r) } b = newBuffer() - b.write(BYTES[:5]) - b.write(BYTES[5:15]) + b.write(alphabet[:5]) + b.write(alphabet[5:15]) r, _ = b.Read(make([]byte, 10)) r2, _ := b.Read(make([]byte, 10)) if r != 10 || r2 != 5 || 15 != r+r2 { @@ -45,14 +45,14 @@ func TestBufferReadwrite(t *testing.T) { func TestBufferClose(t *testing.T) { b := newBuffer() - b.write(BYTES[:10]) + b.write(alphabet[:10]) b.eof() _, err := b.Read(make([]byte, 5)) if err != nil { t.Fatal("expected read of 5 to not return EOF") } b = newBuffer() - b.write(BYTES[:10]) + b.write(alphabet[:10]) b.eof() r, err := b.Read(make([]byte, 5)) r2, err2 := b.Read(make([]byte, 10)) @@ -61,7 +61,7 @@ func TestBufferClose(t *testing.T) { } b = newBuffer() - b.write(BYTES[:10]) + b.write(alphabet[:10]) b.eof() r, err = b.Read(make([]byte, 5)) r2, err2 = b.Read(make([]byte, 10)) diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs.go new file mode 100644 index 00000000..9962ff0f --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs.go @@ -0,0 +1,474 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "sort" + "time" +) + +// These constants from [PROTOCOL.certkeys] represent the algorithm names +// for certificate types supported by this package. +const ( + CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" + CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" + CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" + CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" +) + +// Certificate types distinguish between host and user +// certificates. The values can be set in the CertType field of +// Certificate. +const ( + UserCert = 1 + HostCert = 2 +) + +// Signature represents a cryptographic signature. +type Signature struct { + Format string + Blob []byte +} + +// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that +// a certificate does not expire. +const CertTimeInfinity = 1<<64 - 1 + +// An Certificate represents an OpenSSH certificate as defined in +// [PROTOCOL.certkeys]?rev=1.8. +type Certificate struct { + Nonce []byte + Key PublicKey + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []string + ValidAfter uint64 + ValidBefore uint64 + Permissions + Reserved []byte + SignatureKey PublicKey + Signature *Signature +} + +// genericCertData holds the key-independent part of the certificate data. +// Overall, certificates contain an nonce, public key fields and +// key-independent fields. +type genericCertData struct { + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []byte + ValidAfter uint64 + ValidBefore uint64 + CriticalOptions []byte + Extensions []byte + Reserved []byte + SignatureKey []byte + Signature []byte +} + +func marshalStringList(namelist []string) []byte { + var to []byte + for _, name := range namelist { + s := struct{ N string }{name} + to = append(to, Marshal(&s)...) + } + return to +} + +func marshalTuples(tups map[string]string) []byte { + keys := make([]string, 0, len(tups)) + for k := range tups { + keys = append(keys, k) + } + sort.Strings(keys) + + var r []byte + for _, k := range keys { + s := struct{ K, V string }{k, tups[k]} + r = append(r, Marshal(&s)...) + } + return r +} + +func parseTuples(in []byte) (map[string]string, error) { + tups := map[string]string{} + var lastKey string + var haveLastKey bool + + for len(in) > 0 { + nameBytes, rest, ok := parseString(in) + if !ok { + return nil, errShortRead + } + data, rest, ok := parseString(rest) + if !ok { + return nil, errShortRead + } + name := string(nameBytes) + + // according to [PROTOCOL.certkeys], the names must be in + // lexical order. + if haveLastKey && name <= lastKey { + return nil, fmt.Errorf("ssh: certificate options are not in lexical order") + } + lastKey, haveLastKey = name, true + + tups[name] = string(data) + in = rest + } + return tups, nil +} + +func parseCert(in []byte, privAlgo string) (*Certificate, error) { + nonce, rest, ok := parseString(in) + if !ok { + return nil, errShortRead + } + + key, rest, err := parsePubKey(rest, privAlgo) + if err != nil { + return nil, err + } + + var g genericCertData + if err := Unmarshal(rest, &g); err != nil { + return nil, err + } + + c := &Certificate{ + Nonce: nonce, + Key: key, + Serial: g.Serial, + CertType: g.CertType, + KeyId: g.KeyId, + ValidAfter: g.ValidAfter, + ValidBefore: g.ValidBefore, + } + + for principals := g.ValidPrincipals; len(principals) > 0; { + principal, rest, ok := parseString(principals) + if !ok { + return nil, errShortRead + } + c.ValidPrincipals = append(c.ValidPrincipals, string(principal)) + principals = rest + } + + c.CriticalOptions, err = parseTuples(g.CriticalOptions) + if err != nil { + return nil, err + } + c.Extensions, err = parseTuples(g.Extensions) + if err != nil { + return nil, err + } + c.Reserved = g.Reserved + k, err := ParsePublicKey(g.SignatureKey) + if err != nil { + return nil, err + } + + c.SignatureKey = k + c.Signature, rest, ok = parseSignatureBody(g.Signature) + if !ok || len(rest) > 0 { + return nil, errors.New("ssh: signature parse error") + } + + return c, nil +} + +type openSSHCertSigner struct { + pub *Certificate + signer Signer +} + +// NewCertSigner returns a Signer that signs with the given Certificate, whose +// private key is held by signer. It returns an error if the public key in cert +// doesn't match the key used by signer. +func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) { + if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { + return nil, errors.New("ssh: signer and cert have different public key") + } + + return &openSSHCertSigner{cert, signer}, nil +} + +func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.signer.Sign(rand, data) +} + +func (s *openSSHCertSigner) PublicKey() PublicKey { + return s.pub +} + +const sourceAddressCriticalOption = "source-address" + +// CertChecker does the work of verifying a certificate. Its methods +// can be plugged into ClientConfig.HostKeyCallback and +// ServerConfig.PublicKeyCallback. For the CertChecker to work, +// minimally, the IsAuthority callback should be set. +type CertChecker struct { + // SupportedCriticalOptions lists the CriticalOptions that the + // server application layer understands. These are only used + // for user certificates. + SupportedCriticalOptions []string + + // IsAuthority should return true if the key is recognized as + // an authority. This allows for certificates to be signed by other + // certificates. + IsAuthority func(auth PublicKey) bool + + // Clock is used for verifying time stamps. If nil, time.Now + // is used. + Clock func() time.Time + + // UserKeyFallback is called when CertChecker.Authenticate encounters a + // public key that is not a certificate. It must implement validation + // of user keys or else, if nil, all such keys are rejected. + UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // HostKeyFallback is called when CertChecker.CheckHostKey encounters a + // public key that is not a certificate. It must implement host key + // validation or else, if nil, all such keys are rejected. + HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error + + // IsRevoked is called for each certificate so that revocation checking + // can be implemented. It should return true if the given certificate + // is revoked and false otherwise. If nil, no certificates are + // considered to have been revoked. + IsRevoked func(cert *Certificate) bool +} + +// CheckHostKey checks a host key certificate. This method can be +// plugged into ClientConfig.HostKeyCallback. +func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error { + cert, ok := key.(*Certificate) + if !ok { + if c.HostKeyFallback != nil { + return c.HostKeyFallback(addr, remote, key) + } + return errors.New("ssh: non-certificate host key") + } + if cert.CertType != HostCert { + return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType) + } + + return c.CheckCert(addr, cert) +} + +// Authenticate checks a user certificate. Authenticate can be used as +// a value for ServerConfig.PublicKeyCallback. +func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) { + cert, ok := pubKey.(*Certificate) + if !ok { + if c.UserKeyFallback != nil { + return c.UserKeyFallback(conn, pubKey) + } + return nil, errors.New("ssh: normal key pairs not accepted") + } + + if cert.CertType != UserCert { + return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) + } + + if err := c.CheckCert(conn.User(), cert); err != nil { + return nil, err + } + + return &cert.Permissions, nil +} + +// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and +// the signature of the certificate. +func (c *CertChecker) CheckCert(principal string, cert *Certificate) error { + if c.IsRevoked != nil && c.IsRevoked(cert) { + return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial) + } + + for opt, _ := range cert.CriticalOptions { + // sourceAddressCriticalOption will be enforced by + // serverAuthenticate + if opt == sourceAddressCriticalOption { + continue + } + + found := false + for _, supp := range c.SupportedCriticalOptions { + if supp == opt { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt) + } + } + + if len(cert.ValidPrincipals) > 0 { + // By default, certs are valid for all users/hosts. + found := false + for _, p := range cert.ValidPrincipals { + if p == principal { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals) + } + } + + if !c.IsAuthority(cert.SignatureKey) { + return fmt.Errorf("ssh: certificate signed by unrecognized authority") + } + + clock := c.Clock + if clock == nil { + clock = time.Now + } + + unixNow := clock().Unix() + if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) { + return fmt.Errorf("ssh: cert is not yet valid") + } + if before := int64(cert.ValidBefore); cert.ValidBefore != CertTimeInfinity && (unixNow >= before || before < 0) { + return fmt.Errorf("ssh: cert has expired") + } + if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil { + return fmt.Errorf("ssh: certificate signature does not verify") + } + + return nil +} + +// SignCert sets c.SignatureKey to the authority's public key and stores a +// Signature, by authority, in the certificate. +func (c *Certificate) SignCert(rand io.Reader, authority Signer) error { + c.Nonce = make([]byte, 32) + if _, err := io.ReadFull(rand, c.Nonce); err != nil { + return err + } + c.SignatureKey = authority.PublicKey() + + sig, err := authority.Sign(rand, c.bytesForSigning()) + if err != nil { + return err + } + c.Signature = sig + return nil +} + +var certAlgoNames = map[string]string{ + KeyAlgoRSA: CertAlgoRSAv01, + KeyAlgoDSA: CertAlgoDSAv01, + KeyAlgoECDSA256: CertAlgoECDSA256v01, + KeyAlgoECDSA384: CertAlgoECDSA384v01, + KeyAlgoECDSA521: CertAlgoECDSA521v01, +} + +// certToPrivAlgo returns the underlying algorithm for a certificate algorithm. +// Panics if a non-certificate algorithm is passed. +func certToPrivAlgo(algo string) string { + for privAlgo, pubAlgo := range certAlgoNames { + if pubAlgo == algo { + return privAlgo + } + } + panic("unknown cert algorithm") +} + +func (cert *Certificate) bytesForSigning() []byte { + c2 := *cert + c2.Signature = nil + out := c2.Marshal() + // Drop trailing signature length. + return out[:len(out)-4] +} + +// Marshal serializes c into OpenSSH's wire format. It is part of the +// PublicKey interface. +func (c *Certificate) Marshal() []byte { + generic := genericCertData{ + Serial: c.Serial, + CertType: c.CertType, + KeyId: c.KeyId, + ValidPrincipals: marshalStringList(c.ValidPrincipals), + ValidAfter: uint64(c.ValidAfter), + ValidBefore: uint64(c.ValidBefore), + CriticalOptions: marshalTuples(c.CriticalOptions), + Extensions: marshalTuples(c.Extensions), + Reserved: c.Reserved, + SignatureKey: c.SignatureKey.Marshal(), + } + if c.Signature != nil { + generic.Signature = Marshal(c.Signature) + } + genericBytes := Marshal(&generic) + keyBytes := c.Key.Marshal() + _, keyBytes, _ = parseString(keyBytes) + prefix := Marshal(&struct { + Name string + Nonce []byte + Key []byte `ssh:"rest"` + }{c.Type(), c.Nonce, keyBytes}) + + result := make([]byte, 0, len(prefix)+len(genericBytes)) + result = append(result, prefix...) + result = append(result, genericBytes...) + return result +} + +// Type returns the key name. It is part of the PublicKey interface. +func (c *Certificate) Type() string { + algo, ok := certAlgoNames[c.Key.Type()] + if !ok { + panic("unknown cert key type") + } + return algo +} + +// Verify verifies a signature against the certificate's public +// key. It is part of the PublicKey interface. +func (c *Certificate) Verify(data []byte, sig *Signature) error { + return c.Key.Verify(data, sig) +} + +func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { + format, in, ok := parseString(in) + if !ok { + return + } + + out = &Signature{ + Format: string(format), + } + + if out.Blob, in, ok = parseString(in); !ok { + return + } + + return out, in, ok +} + +func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) { + sigBytes, rest, ok := parseString(in) + if !ok { + return + } + + out, trailing, ok := parseSignatureBody(sigBytes) + if !ok || len(trailing) > 0 { + return nil, nil, false + } + return +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs_test.go new file mode 100644 index 00000000..7d1b00f6 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/certs_test.go @@ -0,0 +1,156 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "testing" + "time" +) + +// Cert generated by ssh-keygen 6.0p1 Debian-4. +// % ssh-keygen -s ca-key -I test user-key +var exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=` + +func TestParseCert(t *testing.T) { + authKeyBytes := []byte(exampleSSHCert) + + key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes) + if err != nil { + t.Fatalf("ParseAuthorizedKey: %v", err) + } + if len(rest) > 0 { + t.Errorf("rest: got %q, want empty", rest) + } + + if _, ok := key.(*Certificate); !ok { + t.Fatalf("got %#v, want *Certificate", key) + } + + marshaled := MarshalAuthorizedKey(key) + // Before comparison, remove the trailing newline that + // MarshalAuthorizedKey adds. + marshaled = marshaled[:len(marshaled)-1] + if !bytes.Equal(authKeyBytes, marshaled) { + t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes) + } +} + +func TestValidateCert(t *testing.T) { + key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert)) + if err != nil { + t.Fatalf("ParseAuthorizedKey: %v", err) + } + validCert, ok := key.(*Certificate) + if !ok { + t.Fatalf("got %v (%T), want *Certificate", key, key) + } + checker := CertChecker{} + checker.IsAuthority = func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal()) + } + + if err := checker.CheckCert("user", validCert); err != nil { + t.Errorf("Unable to validate certificate: %v", err) + } + invalidCert := &Certificate{ + Key: testPublicKeys["rsa"], + SignatureKey: testPublicKeys["ecdsa"], + ValidBefore: CertTimeInfinity, + Signature: &Signature{}, + } + if err := checker.CheckCert("user", invalidCert); err == nil { + t.Error("Invalid cert signature passed validation") + } +} + +func TestValidateCertTime(t *testing.T) { + cert := Certificate{ + ValidPrincipals: []string{"user"}, + Key: testPublicKeys["rsa"], + ValidAfter: 50, + ValidBefore: 100, + } + + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + for ts, ok := range map[int64]bool{ + 25: false, + 50: true, + 99: true, + 100: false, + 125: false, + } { + checker := CertChecker{ + Clock: func() time.Time { return time.Unix(ts, 0) }, + } + checker.IsAuthority = func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), + testPublicKeys["ecdsa"].Marshal()) + } + + if v := checker.CheckCert("user", &cert); (v == nil) != ok { + t.Errorf("Authenticate(%d): %v", ts, v) + } + } +} + +// TODO(hanwen): tests for +// +// host keys: +// * fallbacks + +func TestHostKeyCert(t *testing.T) { + cert := &Certificate{ + ValidPrincipals: []string{"hostname", "hostname.domain"}, + Key: testPublicKeys["rsa"], + ValidBefore: CertTimeInfinity, + CertType: HostCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + checker := &CertChecker{ + IsAuthority: func(p PublicKey) bool { + return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal()) + }, + } + + certSigner, err := NewCertSigner(cert, testSigners["rsa"]) + if err != nil { + t.Errorf("NewCertSigner: %v", err) + } + + for _, name := range []string{"hostname", "otherhost"} { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go func() { + conf := ServerConfig{ + NoClientAuth: true, + } + conf.AddHostKey(certSigner) + _, _, _, err := NewServerConn(c1, &conf) + if err != nil { + t.Fatalf("NewServerConn: %v", err) + } + }() + + config := &ClientConfig{ + User: "user", + HostKeyCallback: checker.CheckHostKey, + } + _, _, _, err = NewClientConn(c2, name, config) + + succeed := name == "hostname" + if (err == nil) != succeed { + t.Fatalf("NewClientConn(%q): %v", name, err) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/channel.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/channel.go new file mode 100644 index 00000000..5403c7e4 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/channel.go @@ -0,0 +1,631 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "sync" +) + +const ( + minPacketLength = 9 + // channelMaxPacket contains the maximum number of bytes that will be + // sent in a single packet. As per RFC 4253, section 6.1, 32k is also + // the minimum. + channelMaxPacket = 1 << 15 + // We follow OpenSSH here. + channelWindowSize = 64 * channelMaxPacket +) + +// NewChannel represents an incoming request to a channel. It must either be +// accepted for use by calling Accept, or rejected by calling Reject. +type NewChannel interface { + // Accept accepts the channel creation request. It returns the Channel + // and a Go channel containing SSH requests. The Go channel must be + // serviced otherwise the Channel will hang. + Accept() (Channel, <-chan *Request, error) + + // Reject rejects the channel creation request. After calling + // this, no other methods on the Channel may be called. + Reject(reason RejectionReason, message string) error + + // ChannelType returns the type of the channel, as supplied by the + // client. + ChannelType() string + + // ExtraData returns the arbitrary payload for this channel, as supplied + // by the client. This data is specific to the channel type. + ExtraData() []byte +} + +// A Channel is an ordered, reliable, flow-controlled, duplex stream +// that is multiplexed over an SSH connection. +type Channel interface { + // Read reads up to len(data) bytes from the channel. + Read(data []byte) (int, error) + + // Write writes len(data) bytes to the channel. + Write(data []byte) (int, error) + + // Close signals end of channel use. No data may be sent after this + // call. + Close() error + + // CloseWrite signals the end of sending in-band + // data. Requests may still be sent, and the other side may + // still send data + CloseWrite() error + + // SendRequest sends a channel request. If wantReply is true, + // it will wait for a reply and return the result as a + // boolean, otherwise the return value will be false. Channel + // requests are out-of-band messages so they may be sent even + // if the data stream is closed or blocked by flow control. + SendRequest(name string, wantReply bool, payload []byte) (bool, error) + + // Stderr returns an io.ReadWriter that writes to this channel + // with the extended data type set to stderr. Stderr may + // safely be read and written from a different goroutine than + // Read and Write respectively. + Stderr() io.ReadWriter +} + +// Request is a request sent outside of the normal stream of +// data. Requests can either be specific to an SSH channel, or they +// can be global. +type Request struct { + Type string + WantReply bool + Payload []byte + + ch *channel + mux *mux +} + +// Reply sends a response to a request. It must be called for all requests +// where WantReply is true and is a no-op otherwise. The payload argument is +// ignored for replies to channel-specific requests. +func (r *Request) Reply(ok bool, payload []byte) error { + if !r.WantReply { + return nil + } + + if r.ch == nil { + return r.mux.ackRequest(ok, payload) + } + + return r.ch.ackRequest(ok) +} + +// RejectionReason is an enumeration used when rejecting channel creation +// requests. See RFC 4254, section 5.1. +type RejectionReason uint32 + +const ( + Prohibited RejectionReason = iota + 1 + ConnectionFailed + UnknownChannelType + ResourceShortage +) + +// String converts the rejection reason to human readable form. +func (r RejectionReason) String() string { + switch r { + case Prohibited: + return "administratively prohibited" + case ConnectionFailed: + return "connect failed" + case UnknownChannelType: + return "unknown channel type" + case ResourceShortage: + return "resource shortage" + } + return fmt.Sprintf("unknown reason %d", int(r)) +} + +func min(a uint32, b int) uint32 { + if a < uint32(b) { + return a + } + return uint32(b) +} + +type channelDirection uint8 + +const ( + channelInbound channelDirection = iota + channelOutbound +) + +// channel is an implementation of the Channel interface that works +// with the mux class. +type channel struct { + // R/O after creation + chanType string + extraData []byte + localId, remoteId uint32 + + // maxIncomingPayload and maxRemotePayload are the maximum + // payload sizes of normal and extended data packets for + // receiving and sending, respectively. The wire packet will + // be 9 or 13 bytes larger (excluding encryption overhead). + maxIncomingPayload uint32 + maxRemotePayload uint32 + + mux *mux + + // decided is set to true if an accept or reject message has been sent + // (for outbound channels) or received (for inbound channels). + decided bool + + // direction contains either channelOutbound, for channels created + // locally, or channelInbound, for channels created by the peer. + direction channelDirection + + // Pending internal channel messages. + msg chan interface{} + + // Since requests have no ID, there can be only one request + // with WantReply=true outstanding. This lock is held by a + // goroutine that has such an outgoing request pending. + sentRequestMu sync.Mutex + + incomingRequests chan *Request + + sentEOF bool + + // thread-safe data + remoteWin window + pending *buffer + extPending *buffer + + // windowMu protects myWindow, the flow-control window. + windowMu sync.Mutex + myWindow uint32 + + // writeMu serializes calls to mux.conn.writePacket() and + // protects sentClose and packetPool. This mutex must be + // different from windowMu, as writePacket can block if there + // is a key exchange pending. + writeMu sync.Mutex + sentClose bool + + // packetPool has a buffer for each extended channel ID to + // save allocations during writes. + packetPool map[uint32][]byte +} + +// writePacket sends a packet. If the packet is a channel close, it updates +// sentClose. This method takes the lock c.writeMu. +func (c *channel) writePacket(packet []byte) error { + c.writeMu.Lock() + if c.sentClose { + c.writeMu.Unlock() + return io.EOF + } + c.sentClose = (packet[0] == msgChannelClose) + err := c.mux.conn.writePacket(packet) + c.writeMu.Unlock() + return err +} + +func (c *channel) sendMessage(msg interface{}) error { + if debugMux { + log.Printf("send %d: %#v", c.mux.chanList.offset, msg) + } + + p := Marshal(msg) + binary.BigEndian.PutUint32(p[1:], c.remoteId) + return c.writePacket(p) +} + +// WriteExtended writes data to a specific extended stream. These streams are +// used, for example, for stderr. +func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) { + if c.sentEOF { + return 0, io.EOF + } + // 1 byte message type, 4 bytes remoteId, 4 bytes data length + opCode := byte(msgChannelData) + headerLength := uint32(9) + if extendedCode > 0 { + headerLength += 4 + opCode = msgChannelExtendedData + } + + c.writeMu.Lock() + packet := c.packetPool[extendedCode] + // We don't remove the buffer from packetPool, so + // WriteExtended calls from different goroutines will be + // flagged as errors by the race detector. + c.writeMu.Unlock() + + for len(data) > 0 { + space := min(c.maxRemotePayload, len(data)) + if space, err = c.remoteWin.reserve(space); err != nil { + return n, err + } + if want := headerLength + space; uint32(cap(packet)) < want { + packet = make([]byte, want) + } else { + packet = packet[:want] + } + + todo := data[:space] + + packet[0] = opCode + binary.BigEndian.PutUint32(packet[1:], c.remoteId) + if extendedCode > 0 { + binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode)) + } + binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo))) + copy(packet[headerLength:], todo) + if err = c.writePacket(packet); err != nil { + return n, err + } + + n += len(todo) + data = data[len(todo):] + } + + c.writeMu.Lock() + c.packetPool[extendedCode] = packet + c.writeMu.Unlock() + + return n, err +} + +func (c *channel) handleData(packet []byte) error { + headerLen := 9 + isExtendedData := packet[0] == msgChannelExtendedData + if isExtendedData { + headerLen = 13 + } + if len(packet) < headerLen { + // malformed data packet + return parseError(packet[0]) + } + + var extended uint32 + if isExtendedData { + extended = binary.BigEndian.Uint32(packet[5:]) + } + + length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen]) + if length == 0 { + return nil + } + if length > c.maxIncomingPayload { + // TODO(hanwen): should send Disconnect? + return errors.New("ssh: incoming packet exceeds maximum payload size") + } + + data := packet[headerLen:] + if length != uint32(len(data)) { + return errors.New("ssh: wrong packet length") + } + + c.windowMu.Lock() + if c.myWindow < length { + c.windowMu.Unlock() + // TODO(hanwen): should send Disconnect with reason? + return errors.New("ssh: remote side wrote too much") + } + c.myWindow -= length + c.windowMu.Unlock() + + if extended == 1 { + c.extPending.write(data) + } else if extended > 0 { + // discard other extended data. + } else { + c.pending.write(data) + } + return nil +} + +func (c *channel) adjustWindow(n uint32) error { + c.windowMu.Lock() + // Since myWindow is managed on our side, and can never exceed + // the initial window setting, we don't worry about overflow. + c.myWindow += uint32(n) + c.windowMu.Unlock() + return c.sendMessage(windowAdjustMsg{ + AdditionalBytes: uint32(n), + }) +} + +func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) { + switch extended { + case 1: + n, err = c.extPending.Read(data) + case 0: + n, err = c.pending.Read(data) + default: + return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended) + } + + if n > 0 { + err = c.adjustWindow(uint32(n)) + // sendWindowAdjust can return io.EOF if the remote + // peer has closed the connection, however we want to + // defer forwarding io.EOF to the caller of Read until + // the buffer has been drained. + if n > 0 && err == io.EOF { + err = nil + } + } + + return n, err +} + +func (c *channel) close() { + c.pending.eof() + c.extPending.eof() + close(c.msg) + close(c.incomingRequests) + c.writeMu.Lock() + // This is not necesary for a normal channel teardown, but if + // there was another error, it is. + c.sentClose = true + c.writeMu.Unlock() + // Unblock writers. + c.remoteWin.close() +} + +// responseMessageReceived is called when a success or failure message is +// received on a channel to check that such a message is reasonable for the +// given channel. +func (c *channel) responseMessageReceived() error { + if c.direction == channelInbound { + return errors.New("ssh: channel response message received on inbound channel") + } + if c.decided { + return errors.New("ssh: duplicate response received for channel") + } + c.decided = true + return nil +} + +func (c *channel) handlePacket(packet []byte) error { + switch packet[0] { + case msgChannelData, msgChannelExtendedData: + return c.handleData(packet) + case msgChannelClose: + c.sendMessage(channelCloseMsg{PeersId: c.remoteId}) + c.mux.chanList.remove(c.localId) + c.close() + return nil + case msgChannelEOF: + // RFC 4254 is mute on how EOF affects dataExt messages but + // it is logical to signal EOF at the same time. + c.extPending.eof() + c.pending.eof() + return nil + } + + decoded, err := decode(packet) + if err != nil { + return err + } + + switch msg := decoded.(type) { + case *channelOpenFailureMsg: + if err := c.responseMessageReceived(); err != nil { + return err + } + c.mux.chanList.remove(msg.PeersId) + c.msg <- msg + case *channelOpenConfirmMsg: + if err := c.responseMessageReceived(); err != nil { + return err + } + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize) + } + c.remoteId = msg.MyId + c.maxRemotePayload = msg.MaxPacketSize + c.remoteWin.add(msg.MyWindow) + c.msg <- msg + case *windowAdjustMsg: + if !c.remoteWin.add(msg.AdditionalBytes) { + return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes) + } + case *channelRequestMsg: + req := Request{ + Type: msg.Request, + WantReply: msg.WantReply, + Payload: msg.RequestSpecificData, + ch: c, + } + + c.incomingRequests <- &req + default: + c.msg <- msg + } + return nil +} + +func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel { + ch := &channel{ + remoteWin: window{Cond: newCond()}, + myWindow: channelWindowSize, + pending: newBuffer(), + extPending: newBuffer(), + direction: direction, + incomingRequests: make(chan *Request, 16), + msg: make(chan interface{}, 16), + chanType: chanType, + extraData: extraData, + mux: m, + packetPool: make(map[uint32][]byte), + } + ch.localId = m.chanList.add(ch) + return ch +} + +var errUndecided = errors.New("ssh: must Accept or Reject channel") +var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once") + +type extChannel struct { + code uint32 + ch *channel +} + +func (e *extChannel) Write(data []byte) (n int, err error) { + return e.ch.WriteExtended(data, e.code) +} + +func (e *extChannel) Read(data []byte) (n int, err error) { + return e.ch.ReadExtended(data, e.code) +} + +func (c *channel) Accept() (Channel, <-chan *Request, error) { + if c.decided { + return nil, nil, errDecidedAlready + } + c.maxIncomingPayload = channelMaxPacket + confirm := channelOpenConfirmMsg{ + PeersId: c.remoteId, + MyId: c.localId, + MyWindow: c.myWindow, + MaxPacketSize: c.maxIncomingPayload, + } + c.decided = true + if err := c.sendMessage(confirm); err != nil { + return nil, nil, err + } + + return c, c.incomingRequests, nil +} + +func (ch *channel) Reject(reason RejectionReason, message string) error { + if ch.decided { + return errDecidedAlready + } + reject := channelOpenFailureMsg{ + PeersId: ch.remoteId, + Reason: reason, + Message: message, + Language: "en", + } + ch.decided = true + return ch.sendMessage(reject) +} + +func (ch *channel) Read(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.ReadExtended(data, 0) +} + +func (ch *channel) Write(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.WriteExtended(data, 0) +} + +func (ch *channel) CloseWrite() error { + if !ch.decided { + return errUndecided + } + ch.sentEOF = true + return ch.sendMessage(channelEOFMsg{ + PeersId: ch.remoteId}) +} + +func (ch *channel) Close() error { + if !ch.decided { + return errUndecided + } + + return ch.sendMessage(channelCloseMsg{ + PeersId: ch.remoteId}) +} + +// Extended returns an io.ReadWriter that sends and receives data on the given, +// SSH extended stream. Such streams are used, for example, for stderr. +func (ch *channel) Extended(code uint32) io.ReadWriter { + if !ch.decided { + return nil + } + return &extChannel{code, ch} +} + +func (ch *channel) Stderr() io.ReadWriter { + return ch.Extended(1) +} + +func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + if !ch.decided { + return false, errUndecided + } + + if wantReply { + ch.sentRequestMu.Lock() + defer ch.sentRequestMu.Unlock() + } + + msg := channelRequestMsg{ + PeersId: ch.remoteId, + Request: name, + WantReply: wantReply, + RequestSpecificData: payload, + } + + if err := ch.sendMessage(msg); err != nil { + return false, err + } + + if wantReply { + m, ok := (<-ch.msg) + if !ok { + return false, io.EOF + } + switch m.(type) { + case *channelRequestFailureMsg: + return false, nil + case *channelRequestSuccessMsg: + return true, nil + default: + return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m) + } + } + + return false, nil +} + +// ackRequest either sends an ack or nack to the channel request. +func (ch *channel) ackRequest(ok bool) error { + if !ch.decided { + return errUndecided + } + + var msg interface{} + if !ok { + msg = channelRequestFailureMsg{ + PeersId: ch.remoteId, + } + } else { + msg = channelRequestSuccessMsg{ + PeersId: ch.remoteId, + } + } + return ch.sendMessage(msg) +} + +func (ch *channel) ChannelType() string { + return ch.chanType +} + +func (ch *channel) ExtraData() []byte { + return ch.extraData +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher.go new file mode 100644 index 00000000..642696bb --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher.go @@ -0,0 +1,344 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rc4" + "crypto/subtle" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" +) + +const ( + packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. + + // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations + // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC + // indicates implementations SHOULD be able to handle larger packet sizes, but then + // waffles on about reasonable limits. + // + // OpenSSH caps their maxPacket at 256kB so we choose to do + // the same. maxPacket is also used to ensure that uint32 + // length fields do not overflow, so it should remain well + // below 4G. + maxPacket = 256 * 1024 +) + +// noneCipher implements cipher.Stream and provides no encryption. It is used +// by the transport before the first key-exchange. +type noneCipher struct{} + +func (c noneCipher) XORKeyStream(dst, src []byte) { + copy(dst, src) +} + +func newAESCTR(key, iv []byte) (cipher.Stream, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewCTR(c, iv), nil +} + +func newRC4(key, iv []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) +} + +type streamCipherMode struct { + keySize int + ivSize int + skip int + createFunc func(key, iv []byte) (cipher.Stream, error) +} + +func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) { + if len(key) < c.keySize { + panic("ssh: key length too small for cipher") + } + if len(iv) < c.ivSize { + panic("ssh: iv too small for cipher") + } + + stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize]) + if err != nil { + return nil, err + } + + var streamDump []byte + if c.skip > 0 { + streamDump = make([]byte, 512) + } + + for remainingToDump := c.skip; remainingToDump > 0; { + dumpThisTime := remainingToDump + if dumpThisTime > len(streamDump) { + dumpThisTime = len(streamDump) + } + stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) + remainingToDump -= dumpThisTime + } + + return stream, nil +} + +// cipherModes documents properties of supported ciphers. Ciphers not included +// are not supported and will not be negotiated, even if explicitly requested in +// ClientConfig.Crypto.Ciphers. +var cipherModes = map[string]*streamCipherMode{ + // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms + // are defined in the order specified in the RFC. + "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR}, + "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR}, + "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR}, + + // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. + // They are defined in the order specified in the RFC. + "arcfour128": {16, 0, 1536, newRC4}, + "arcfour256": {32, 0, 1536, newRC4}, + + // Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol. + // Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and + // RC4) has problems with weak keys, and should be used with caution." + // RFC4345 introduces improved versions of Arcfour. + "arcfour": {16, 0, 0, newRC4}, + + // AES-GCM is not a stream cipher, so it is constructed with a + // special case. If we add any more non-stream ciphers, we + // should invest a cleaner way to do this. + gcmCipherID: {16, 12, 0, nil}, +} + +// prefixLen is the length of the packet prefix that contains the packet length +// and number of padding bytes. +const prefixLen = 5 + +// streamPacketCipher is a packetCipher using a stream cipher. +type streamPacketCipher struct { + mac hash.Hash + cipher cipher.Stream + + // The following members are to avoid per-packet allocations. + prefix [prefixLen]byte + seqNumBytes [4]byte + padding [2 * packetSizeMultiple]byte + packetData []byte + macResult []byte +} + +// readPacket reads and decrypt a single packet from the reader argument. +func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, s.prefix[:]); err != nil { + return nil, err + } + + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + length := binary.BigEndian.Uint32(s.prefix[0:4]) + paddingLength := uint32(s.prefix[4]) + + var macSize uint32 + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + s.mac.Write(s.prefix[:]) + macSize = uint32(s.mac.Size()) + } + + if length <= paddingLength+1 { + return nil, errors.New("ssh: invalid packet length, packet too small") + } + + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + // the maxPacket check above ensures that length-1+macSize + // does not overflow. + if uint32(cap(s.packetData)) < length-1+macSize { + s.packetData = make([]byte, length-1+macSize) + } else { + s.packetData = s.packetData[:length-1+macSize] + } + + if _, err := io.ReadFull(r, s.packetData); err != nil { + return nil, err + } + mac := s.packetData[length-1:] + data := s.packetData[:length-1] + s.cipher.XORKeyStream(data, data) + + if s.mac != nil { + s.mac.Write(data) + s.macResult = s.mac.Sum(s.macResult[:0]) + if subtle.ConstantTimeCompare(s.macResult, mac) != 1 { + return nil, errors.New("ssh: MAC failure") + } + } + + return s.packetData[:length-paddingLength-1], nil +} + +// writePacket encrypts and sends a packet of data to the writer argument +func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + if len(packet) > maxPacket { + return errors.New("ssh: packet too large") + } + + paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple + if paddingLength < 4 { + paddingLength += packetSizeMultiple + } + + length := len(packet) + 1 + paddingLength + binary.BigEndian.PutUint32(s.prefix[:], uint32(length)) + s.prefix[4] = byte(paddingLength) + padding := s.padding[:paddingLength] + if _, err := io.ReadFull(rand, padding); err != nil { + return err + } + + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + s.mac.Write(s.prefix[:]) + s.mac.Write(packet) + s.mac.Write(padding) + } + + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + s.cipher.XORKeyStream(packet, packet) + s.cipher.XORKeyStream(padding, padding) + + if _, err := w.Write(s.prefix[:]); err != nil { + return err + } + if _, err := w.Write(packet); err != nil { + return err + } + if _, err := w.Write(padding); err != nil { + return err + } + + if s.mac != nil { + s.macResult = s.mac.Sum(s.macResult[:0]) + if _, err := w.Write(s.macResult); err != nil { + return err + } + } + + return nil +} + +type gcmCipher struct { + aead cipher.AEAD + prefix [4]byte + iv []byte + buf []byte +} + +func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aead, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + return &gcmCipher{ + aead: aead, + iv: iv, + }, nil +} + +const gcmTagSize = 16 + +func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + // Pad out to multiple of 16 bytes. This is different from the + // stream cipher because that encrypts the length too. + padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple) + if padding < 4 { + padding += packetSizeMultiple + } + + length := uint32(len(packet) + int(padding) + 1) + binary.BigEndian.PutUint32(c.prefix[:], length) + if _, err := w.Write(c.prefix[:]); err != nil { + return err + } + + if cap(c.buf) < int(length) { + c.buf = make([]byte, length) + } else { + c.buf = c.buf[:length] + } + + c.buf[0] = padding + copy(c.buf[1:], packet) + if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil { + return err + } + c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if _, err := w.Write(c.buf); err != nil { + return err + } + c.incIV() + + return nil +} + +func (c *gcmCipher) incIV() { + for i := 4 + 7; i >= 4; i-- { + c.iv[i]++ + if c.iv[i] != 0 { + break + } + } +} + +func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, c.prefix[:]); err != nil { + return nil, err + } + length := binary.BigEndian.Uint32(c.prefix[:]) + if length > maxPacket { + return nil, errors.New("ssh: max packet length exceeded.") + } + + if cap(c.buf) < int(length+gcmTagSize) { + c.buf = make([]byte, length+gcmTagSize) + } else { + c.buf = c.buf[:length+gcmTagSize] + } + + if _, err := io.ReadFull(r, c.buf); err != nil { + return nil, err + } + + plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if err != nil { + return nil, err + } + c.incIV() + + padding := plain[0] + if padding < 4 || padding >= 20 { + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding+1) >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + plain = plain[1 : length-uint32(padding)] + return plain, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher_test.go new file mode 100644 index 00000000..e279af04 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/cipher_test.go @@ -0,0 +1,59 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto" + "crypto/rand" + "testing" +) + +func TestDefaultCiphersExist(t *testing.T) { + for _, cipherAlgo := range supportedCiphers { + if _, ok := cipherModes[cipherAlgo]; !ok { + t.Errorf("default cipher %q is unknown", cipherAlgo) + } + } +} + +func TestPacketCiphers(t *testing.T) { + for cipher := range cipherModes { + kr := &kexResult{Hash: crypto.SHA1} + algs := directionAlgorithms{ + Cipher: cipher, + MAC: "hmac-sha1", + Compression: "none", + } + client, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Errorf("newPacketCipher(client, %q): %v", cipher, err) + continue + } + server, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Errorf("newPacketCipher(client, %q): %v", cipher, err) + continue + } + + want := "bla bla" + input := []byte(want) + buf := &bytes.Buffer{} + if err := client.writePacket(0, buf, rand.Reader, input); err != nil { + t.Errorf("writePacket(%q): %v", cipher, err) + continue + } + + packet, err := server.readPacket(0, buf) + if err != nil { + t.Errorf("readPacket(%q): %v", cipher, err) + continue + } + + if string(packet) != want { + t.Errorf("roundtrip(%q): got %q, want %q", cipher, packet, want) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client.go new file mode 100644 index 00000000..03c4e77d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client.go @@ -0,0 +1,202 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "fmt" + "net" + "sync" +) + +// Client implements a traditional SSH client that supports shells, +// subprocesses, port forwarding and tunneled dialing. +type Client struct { + Conn + + forwards forwardList // forwarded tcpip connections from the remote side + mu sync.Mutex + channelHandlers map[string]chan NewChannel +} + +// HandleChannelOpen returns a channel on which NewChannel requests +// for the given type are sent. If the type already is being handled, +// nil is returned. The channel is closed when the connection is closed. +func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel { + c.mu.Lock() + defer c.mu.Unlock() + if c.channelHandlers == nil { + // The SSH channel has been closed. + c := make(chan NewChannel) + close(c) + return c + } + + ch := c.channelHandlers[channelType] + if ch != nil { + return nil + } + + ch = make(chan NewChannel, 16) + c.channelHandlers[channelType] = ch + return ch +} + +// NewClient creates a Client on top of the given connection. +func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { + conn := &Client{ + Conn: c, + channelHandlers: make(map[string]chan NewChannel, 1), + } + + go conn.handleGlobalRequests(reqs) + go conn.handleChannelOpens(chans) + go func() { + conn.Wait() + conn.forwards.closeAll() + }() + go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip")) + return conn +} + +// NewClientConn establishes an authenticated SSH connection using c +// as the underlying transport. The Request and NewChannel channels +// must be serviced or the connection will hang. +func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + conn := &connection{ + sshConn: sshConn{conn: c}, + } + + if err := conn.clientHandshake(addr, &fullConf); err != nil { + c.Close() + return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err) + } + conn.mux = newMux(conn.transport) + return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil +} + +// clientHandshake performs the client side key exchange. See RFC 4253 Section +// 7. +func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error { + c.clientVersion = []byte(packageVersion) + if config.ClientVersion != "" { + c.clientVersion = []byte(config.ClientVersion) + } + + var err error + c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion) + if err != nil { + return err + } + + c.transport = newClientTransport( + newTransport(c.sshConn.conn, config.Rand, true /* is client */), + c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr()) + if err := c.transport.requestKeyChange(); err != nil { + return err + } + + if packet, err := c.transport.readPacket(); err != nil { + return err + } else if packet[0] != msgNewKeys { + return unexpectedMessageError(msgNewKeys, packet[0]) + } + return c.clientAuthenticate(config) +} + +// verifyHostKeySignature verifies the host key obtained in the key +// exchange. +func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error { + sig, rest, ok := parseSignatureBody(result.Signature) + if len(rest) > 0 || !ok { + return errors.New("ssh: signature parse error") + } + + return hostKey.Verify(result.H, sig) +} + +// NewSession opens a new Session for this client. (A session is a remote +// execution of a program.) +func (c *Client) NewSession() (*Session, error) { + ch, in, err := c.OpenChannel("session", nil) + if err != nil { + return nil, err + } + return newSession(ch, in) +} + +func (c *Client) handleGlobalRequests(incoming <-chan *Request) { + for r := range incoming { + // This handles keepalive messages and matches + // the behaviour of OpenSSH. + r.Reply(false, nil) + } +} + +// handleChannelOpens channel open messages from the remote side. +func (c *Client) handleChannelOpens(in <-chan NewChannel) { + for ch := range in { + c.mu.Lock() + handler := c.channelHandlers[ch.ChannelType()] + c.mu.Unlock() + + if handler != nil { + handler <- ch + } else { + ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType())) + } + } + + c.mu.Lock() + for _, ch := range c.channelHandlers { + close(ch) + } + c.channelHandlers = nil + c.mu.Unlock() +} + +// Dial starts a client connection to the given SSH server. It is a +// convenience function that connects to the given network address, +// initiates the SSH handshake, and then sets up a Client. For access +// to incoming channels and requests, use net.Dial with NewClientConn +// instead. +func Dial(network, addr string, config *ClientConfig) (*Client, error) { + conn, err := net.Dial(network, addr) + if err != nil { + return nil, err + } + c, chans, reqs, err := NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return NewClient(c, chans, reqs), nil +} + +// A ClientConfig structure is used to configure a Client. It must not be +// modified after having been passed to an SSH function. +type ClientConfig struct { + // Config contains configuration that is shared between clients and + // servers. + Config + + // User contains the username to authenticate as. + User string + + // Auth contains possible authentication methods to use with the + // server. Only the first instance of a particular RFC 4252 method will + // be used during authentication. + Auth []AuthMethod + + // HostKeyCallback, if not nil, is called during the cryptographic + // handshake to validate the server's host key. A nil HostKeyCallback + // implies that all host keys are accepted. + HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + + // ClientVersion contains the version identification string that will + // be used for the connection. If empty, a reasonable default is used. + ClientVersion string +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth.go new file mode 100644 index 00000000..e15be3ef --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth.go @@ -0,0 +1,441 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" +) + +// clientAuthenticate authenticates with the remote server. See RFC 4252. +func (c *connection) clientAuthenticate(config *ClientConfig) error { + // initiate user auth session + if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil { + return err + } + packet, err := c.transport.readPacket() + if err != nil { + return err + } + var serviceAccept serviceAcceptMsg + if err := Unmarshal(packet, &serviceAccept); err != nil { + return err + } + + // during the authentication phase the client first attempts the "none" method + // then any untried methods suggested by the server. + tried := make(map[string]bool) + var lastMethods []string + for auth := AuthMethod(new(noneAuth)); auth != nil; { + ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand) + if err != nil { + return err + } + if ok { + // success + return nil + } + tried[auth.method()] = true + if methods == nil { + methods = lastMethods + } + lastMethods = methods + + auth = nil + + findNext: + for _, a := range config.Auth { + candidateMethod := a.method() + if tried[candidateMethod] { + continue + } + for _, meth := range methods { + if meth == candidateMethod { + auth = a + break findNext + } + } + } + } + return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried)) +} + +func keys(m map[string]bool) []string { + s := make([]string, 0, len(m)) + + for key := range m { + s = append(s, key) + } + return s +} + +// An AuthMethod represents an instance of an RFC 4252 authentication method. +type AuthMethod interface { + // auth authenticates user over transport t. + // Returns true if authentication is successful. + // If authentication is not successful, a []string of alternative + // method names is returned. If the slice is nil, it will be ignored + // and the previous set of possible methods will be reused. + auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error) + + // method returns the RFC 4252 method name. + method() string +} + +// "none" authentication, RFC 4252 section 5.2. +type noneAuth int + +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + if err := c.writePacket(Marshal(&userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: "none", + })); err != nil { + return false, nil, err + } + + return handleAuthResponse(c) +} + +func (n *noneAuth) method() string { + return "none" +} + +// passwordCallback is an AuthMethod that fetches the password through +// a function call, e.g. by prompting the user. +type passwordCallback func() (password string, err error) + +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + type passwordAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + Reply bool + Password string + } + + pw, err := cb() + // REVIEW NOTE: is there a need to support skipping a password attempt? + // The program may only find out that the user doesn't have a password + // when prompting. + if err != nil { + return false, nil, err + } + + if err := c.writePacket(Marshal(&passwordAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + Reply: false, + Password: pw, + })); err != nil { + return false, nil, err + } + + return handleAuthResponse(c) +} + +func (cb passwordCallback) method() string { + return "password" +} + +// Password returns an AuthMethod using the given password. +func Password(secret string) AuthMethod { + return passwordCallback(func() (string, error) { return secret, nil }) +} + +// PasswordCallback returns an AuthMethod that uses a callback for +// fetching a password. +func PasswordCallback(prompt func() (secret string, err error)) AuthMethod { + return passwordCallback(prompt) +} + +type publickeyAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + // HasSig indicates to the receiver packet that the auth request is signed and + // should be used for authentication of the request. + HasSig bool + Algoname string + PubKey []byte + // Sig is tagged with "rest" so Marshal will exclude it during + // validateKey + Sig []byte `ssh:"rest"` +} + +// publicKeyCallback is an AuthMethod that uses a set of key +// pairs for authentication. +type publicKeyCallback func() ([]Signer, error) + +func (cb publicKeyCallback) method() string { + return "publickey" +} + +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + // Authentication is performed in two stages. The first stage sends an + // enquiry to test if each key is acceptable to the remote. The second + // stage attempts to authenticate with the valid keys obtained in the + // first stage. + + signers, err := cb() + if err != nil { + return false, nil, err + } + var validKeys []Signer + for _, signer := range signers { + if ok, err := validateKey(signer.PublicKey(), user, c); ok { + validKeys = append(validKeys, signer) + } else { + if err != nil { + return false, nil, err + } + } + } + + // methods that may continue if this auth is not successful. + var methods []string + for _, signer := range validKeys { + pub := signer.PublicKey() + + pubKey := pub.Marshal() + sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + }, []byte(pub.Type()), pubKey)) + if err != nil { + return false, nil, err + } + + // manually wrap the serialized signature in a string + s := Marshal(sign) + sig := make([]byte, stringLength(len(s))) + marshalString(sig, s) + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + HasSig: true, + Algoname: pub.Type(), + PubKey: pubKey, + Sig: sig, + } + p := Marshal(&msg) + if err := c.writePacket(p); err != nil { + return false, nil, err + } + var success bool + success, methods, err = handleAuthResponse(c) + if err != nil { + return false, nil, err + } + if success { + return success, methods, err + } + } + return false, methods, nil +} + +// validateKey validates the key provided is acceptable to the server. +func validateKey(key PublicKey, user string, c packetConn) (bool, error) { + pubKey := key.Marshal() + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: "publickey", + HasSig: false, + Algoname: key.Type(), + PubKey: pubKey, + } + if err := c.writePacket(Marshal(&msg)); err != nil { + return false, err + } + + return confirmKeyAck(key, c) +} + +func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { + pubKey := key.Marshal() + algoname := key.Type() + + for { + packet, err := c.readPacket() + if err != nil { + return false, err + } + switch packet[0] { + case msgUserAuthBanner: + // TODO(gpaul): add callback to present the banner to the user + case msgUserAuthPubKeyOk: + var msg userAuthPubKeyOkMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, err + } + if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) { + return false, nil + } + return true, nil + case msgUserAuthFailure: + return false, nil + default: + return false, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +// PublicKeys returns an AuthMethod that uses the given key +// pairs. +func PublicKeys(signers ...Signer) AuthMethod { + return publicKeyCallback(func() ([]Signer, error) { return signers, nil }) +} + +// PublicKeysCallback returns an AuthMethod that runs the given +// function to obtain a list of key pairs. +func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod { + return publicKeyCallback(getSigners) +} + +// handleAuthResponse returns whether the preceding authentication request succeeded +// along with a list of remaining authentication methods to try next and +// an error if an unexpected response was received. +func handleAuthResponse(c packetConn) (bool, []string, error) { + for { + packet, err := c.readPacket() + if err != nil { + return false, nil, err + } + + switch packet[0] { + case msgUserAuthBanner: + // TODO: add callback to present the banner to the user + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + return false, msg.Methods, nil + case msgUserAuthSuccess: + return true, nil, nil + case msgDisconnect: + return false, nil, io.EOF + default: + return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +// KeyboardInteractiveChallenge should print questions, optionally +// disabling echoing (e.g. for passwords), and return all the answers. +// Challenge may be called multiple times in a single session. After +// successful authentication, the server may send a challenge with no +// questions, for which the user and instruction messages should be +// printed. RFC 4256 section 3.3 details how the UI should behave for +// both CLI and GUI environments. +type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) + +// KeyboardInteractive returns a AuthMethod using a prompt/response +// sequence controlled by the server. +func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { + return challenge +} + +func (cb KeyboardInteractiveChallenge) method() string { + return "keyboard-interactive" +} + +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + type initiateMsg struct { + User string `sshtype:"50"` + Service string + Method string + Language string + Submethods string + } + + if err := c.writePacket(Marshal(&initiateMsg{ + User: user, + Service: serviceSSH, + Method: "keyboard-interactive", + })); err != nil { + return false, nil, err + } + + for { + packet, err := c.readPacket() + if err != nil { + return false, nil, err + } + + // like handleAuthResponse, but with less options. + switch packet[0] { + case msgUserAuthBanner: + // TODO: Print banners during userauth. + continue + case msgUserAuthInfoRequest: + // OK + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + return false, msg.Methods, nil + case msgUserAuthSuccess: + return true, nil, nil + default: + return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + + var msg userAuthInfoRequestMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + + // Manually unpack the prompt/echo pairs. + rest := msg.Prompts + var prompts []string + var echos []bool + for i := 0; i < int(msg.NumPrompts); i++ { + prompt, r, ok := parseString(rest) + if !ok || len(r) == 0 { + return false, nil, errors.New("ssh: prompt format error") + } + prompts = append(prompts, string(prompt)) + echos = append(echos, r[0] != 0) + rest = r[1:] + } + + if len(rest) != 0 { + return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs") + } + + answers, err := cb(msg.User, msg.Instruction, prompts, echos) + if err != nil { + return false, nil, err + } + + if len(answers) != len(prompts) { + return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") + } + responseLength := 1 + 4 + for _, a := range answers { + responseLength += stringLength(len(a)) + } + serialized := make([]byte, responseLength) + p := serialized + p[0] = msgUserAuthInfoResponse + p = p[1:] + p = marshalUint32(p, uint32(len(answers))) + for _, a := range answers { + p = marshalString(p, []byte(a)) + } + + if err := c.writePacket(serialized); err != nil { + return false, nil, err + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth_test.go new file mode 100644 index 00000000..c92b5878 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_auth_test.go @@ -0,0 +1,393 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "strings" + "testing" +) + +type keyboardInteractive map[string]string + +func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) { + var answers []string + for _, q := range questions { + answers = append(answers, cr[q]) + } + return answers, nil +} + +// reused internally by tests +var clientPassword = "tiger" + +// tryAuth runs a handshake with a given config against an SSH server +// with config serverConfig +func tryAuth(t *testing.T, config *ClientConfig) error { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + certChecker := CertChecker{ + IsAuthority: func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal()) + }, + UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { + if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { + return nil, nil + } + + return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User()) + }, + IsRevoked: func(c *Certificate) bool { + return c.Serial == 666 + }, + } + + serverConfig := &ServerConfig{ + PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) { + if conn.User() == "testuser" && string(pass) == clientPassword { + return nil, nil + } + return nil, errors.New("password auth failed") + }, + PublicKeyCallback: certChecker.Authenticate, + KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) { + ans, err := challenge("user", + "instruction", + []string{"question1", "question2"}, + []bool{true, true}) + if err != nil { + return nil, err + } + ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2" + if ok { + challenge("user", "motd", nil, nil) + return nil, nil + } + return nil, errors.New("keyboard-interactive failed") + }, + AuthLogCallback: func(conn ConnMetadata, method string, err error) { + t.Logf("user %q, method %q: %v", conn.User(), method, err) + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + go newServer(c1, serverConfig) + _, _, _, err = NewClientConn(c2, "", config) + return err +} + +func TestClientAuthPublicKey(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodPassword(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + Password(clientPassword), + }, + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodFallback(t *testing.T) { + var passwordCalled bool + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + PasswordCallback( + func() (string, error) { + passwordCalled = true + return "WRONG", nil + }), + }, + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + + if passwordCalled { + t.Errorf("password auth tried before public-key auth.") + } +} + +func TestAuthMethodWrongPassword(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + Password("wrong"), + PublicKeys(testSigners["rsa"]), + }, + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodKeyboardInteractive(t *testing.T) { + answers := keyboardInteractive(map[string]string{ + "question1": "answer1", + "question2": "answer2", + }) + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + KeyboardInteractive(answers.Challenge), + }, + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodWrongKeyboardInteractive(t *testing.T) { + answers := keyboardInteractive(map[string]string{ + "question1": "answer1", + "question2": "WRONG", + }) + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + KeyboardInteractive(answers.Challenge), + }, + } + + if err := tryAuth(t, config); err == nil { + t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive") + } +} + +// the mock server will only authenticate ssh-rsa keys +func TestAuthMethodInvalidPublicKey(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["dsa"]), + }, + } + + if err := tryAuth(t, config); err == nil { + t.Fatalf("dsa private key should not have authenticated with rsa public key") + } +} + +// the client should authenticate with the second key +func TestAuthMethodRSAandDSA(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["dsa"], testSigners["rsa"]), + }, + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("client could not authenticate with rsa key: %v", err) + } +} + +func TestClientHMAC(t *testing.T) { + for _, mac := range supportedMACs { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + Config: Config{ + MACs: []string{mac}, + }, + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err) + } + } +} + +// issue 4285. +func TestClientUnsupportedCipher(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(), + }, + Config: Config{ + Ciphers: []string{"aes128-cbc"}, // not currently supported + }, + } + if err := tryAuth(t, config); err == nil { + t.Errorf("expected no ciphers in common") + } +} + +func TestClientUnsupportedKex(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(), + }, + Config: Config{ + KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported + }, + } + if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "no common algorithms") { + t.Errorf("got %v, expected 'no common algorithms'", err) + } +} + +func TestClientLoginCert(t *testing.T) { + cert := &Certificate{ + Key: testPublicKeys["rsa"], + ValidBefore: CertTimeInfinity, + CertType: UserCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + certSigner, err := NewCertSigner(cert, testSigners["rsa"]) + if err != nil { + t.Fatalf("NewCertSigner: %v", err) + } + + clientConfig := &ClientConfig{ + User: "user", + } + clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner)) + + t.Log("should succeed") + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login failed: %v", err) + } + + t.Log("corrupted signature") + cert.Signature.Blob[0]++ + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with corrupted sig") + } + + t.Log("revoked") + cert.Serial = 666 + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("revoked cert login succeeded") + } + cert.Serial = 1 + + t.Log("sign with wrong key") + cert.SignCert(rand.Reader, testSigners["dsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with non-authoritive key") + } + + t.Log("host cert") + cert.CertType = HostCert + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with wrong type") + } + cert.CertType = UserCert + + t.Log("principal specified") + cert.ValidPrincipals = []string{"user"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login failed: %v", err) + } + + t.Log("wrong principal specified") + cert.ValidPrincipals = []string{"fred"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with wrong principal") + } + cert.ValidPrincipals = nil + + t.Log("added critical option") + cert.CriticalOptions = map[string]string{"root-access": "yes"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with unrecognized critical option") + } + + t.Log("allowed source address") + cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login with source-address failed: %v", err) + } + + t.Log("disallowed source address") + cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login with source-address succeeded") + } +} + +func testPermissionsPassing(withPermissions bool, t *testing.T) { + serverConfig := &ServerConfig{ + PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { + if conn.User() == "nopermissions" { + return nil, nil + } else { + return &Permissions{}, nil + } + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + clientConfig := &ClientConfig{ + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + } + if withPermissions { + clientConfig.User = "permissions" + } else { + clientConfig.User = "nopermissions" + } + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewClientConn(c2, "", clientConfig) + serverConn, err := newServer(c1, serverConfig) + if err != nil { + t.Fatal(err) + } + if p := serverConn.Permissions; (p != nil) != withPermissions { + t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p) + } +} + +func TestPermissionsPassing(t *testing.T) { + testPermissionsPassing(true, t) +} + +func TestNoPermissionsPassing(t *testing.T) { + testPermissionsPassing(false, t) +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_test.go similarity index 76% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_test.go index f6c11b95..1fe790cb 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/client_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/client_test.go @@ -1,3 +1,7 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package ssh import ( @@ -7,6 +11,7 @@ import ( func testClientVersion(t *testing.T, config *ClientConfig, expected string) { clientConn, serverConn := net.Pipe() + defer clientConn.Close() receivedVersion := make(chan string, 1) go func() { version, err := readVersion(serverConn) @@ -17,7 +22,7 @@ func testClientVersion(t *testing.T, config *ClientConfig, expected string) { } serverConn.Close() }() - Client(clientConn, config) + NewClientConn(clientConn, "", config) actual := <-receivedVersion if actual != expected { t.Fatalf("got %s; want %s", actual, expected) diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/common.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/common.go new file mode 100644 index 00000000..2fd7fd92 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/common.go @@ -0,0 +1,357 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/rand" + "fmt" + "io" + "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" +) + +// These are string constants in the SSH protocol. +const ( + compressionNone = "none" + serviceUserAuth = "ssh-userauth" + serviceSSH = "ssh-connection" +) + +// supportedCiphers specifies the supported ciphers in preference order. +var supportedCiphers = []string{ + "aes128-ctr", "aes192-ctr", "aes256-ctr", + "aes128-gcm@openssh.com", + "arcfour256", "arcfour128", +} + +// supportedKexAlgos specifies the supported key-exchange algorithms in +// preference order. +var supportedKexAlgos = []string{ + // P384 and P521 are not constant-time yet, but since we don't + // reuse ephemeral keys, using them for ECDH should be OK. + kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, + kexAlgoDH14SHA1, kexAlgoDH1SHA1, +} + +// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods +// of authenticating servers) in preference order. +var supportedHostKeyAlgos = []string{ + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, + CertAlgoECDSA384v01, CertAlgoECDSA521v01, + + KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, + KeyAlgoRSA, KeyAlgoDSA, +} + +// supportedMACs specifies a default set of MAC algorithms in preference order. +// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed +// because they have reached the end of their useful life. +var supportedMACs = []string{ + "hmac-sha1", "hmac-sha1-96", +} + +var supportedCompressions = []string{compressionNone} + +// hashFuncs keeps the mapping of supported algorithms to their respective +// hashes needed for signature verification. +var hashFuncs = map[string]crypto.Hash{ + KeyAlgoRSA: crypto.SHA1, + KeyAlgoDSA: crypto.SHA1, + KeyAlgoECDSA256: crypto.SHA256, + KeyAlgoECDSA384: crypto.SHA384, + KeyAlgoECDSA521: crypto.SHA512, + CertAlgoRSAv01: crypto.SHA1, + CertAlgoDSAv01: crypto.SHA1, + CertAlgoECDSA256v01: crypto.SHA256, + CertAlgoECDSA384v01: crypto.SHA384, + CertAlgoECDSA521v01: crypto.SHA512, +} + +// unexpectedMessageError results when the SSH message that we received didn't +// match what we wanted. +func unexpectedMessageError(expected, got uint8) error { + return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected) +} + +// parseError results from a malformed SSH message. +func parseError(tag uint8) error { + return fmt.Errorf("ssh: parse error in message type %d", tag) +} + +func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) { + for _, clientAlgo := range clientAlgos { + for _, serverAlgo := range serverAlgos { + if clientAlgo == serverAlgo { + return clientAlgo, true + } + } + } + return +} + +func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) { + for _, clientCipher := range clientCiphers { + for _, serverCipher := range serverCiphers { + // reject the cipher if we have no cipherModes definition + if clientCipher == serverCipher && cipherModes[clientCipher] != nil { + return clientCipher, true + } + } + } + return +} + +type directionAlgorithms struct { + Cipher string + MAC string + Compression string +} + +type algorithms struct { + kex string + hostKey string + w directionAlgorithms + r directionAlgorithms +} + +func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) { + var ok bool + result := &algorithms{} + result.kex, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos) + if !ok { + return + } + + result.hostKey, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) + if !ok { + return + } + + result.w.Cipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) + if !ok { + return + } + + result.r.Cipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) + if !ok { + return + } + + result.w.MAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) + if !ok { + return + } + + result.r.MAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) + if !ok { + return + } + + result.w.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) + if !ok { + return + } + + result.r.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) + if !ok { + return + } + + return result +} + +// If rekeythreshold is too small, we can't make any progress sending +// stuff. +const minRekeyThreshold uint64 = 256 + +// Config contains configuration data common to both ServerConfig and +// ClientConfig. +type Config struct { + // Rand provides the source of entropy for cryptographic + // primitives. If Rand is nil, the cryptographic random reader + // in package crypto/rand will be used. + Rand io.Reader + + // The maximum number of bytes sent or received after which a + // new key is negotiated. It must be at least 256. If + // unspecified, 1 gigabyte is used. + RekeyThreshold uint64 + + // The allowed key exchanges algorithms. If unspecified then a + // default set of algorithms is used. + KeyExchanges []string + + // The allowed cipher algorithms. If unspecified then a sensible + // default is used. + Ciphers []string + + // The allowed MAC algorithms. If unspecified then a sensible default + // is used. + MACs []string +} + +// SetDefaults sets sensible values for unset fields in config. This is +// exported for testing: Configs passed to SSH functions are copied and have +// default values set automatically. +func (c *Config) SetDefaults() { + if c.Rand == nil { + c.Rand = rand.Reader + } + if c.Ciphers == nil { + c.Ciphers = supportedCiphers + } + + if c.KeyExchanges == nil { + c.KeyExchanges = supportedKexAlgos + } + + if c.MACs == nil { + c.MACs = supportedMACs + } + + if c.RekeyThreshold == 0 { + // RFC 4253, section 9 suggests rekeying after 1G. + c.RekeyThreshold = 1 << 30 + } + if c.RekeyThreshold < minRekeyThreshold { + c.RekeyThreshold = minRekeyThreshold + } +} + +// buildDataSignedForAuth returns the data that is signed in order to prove +// possession of a private key. See RFC 4252, section 7. +func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte { + data := struct { + Session []byte + Type byte + User string + Service string + Method string + Sign bool + Algo []byte + PubKey []byte + }{ + sessionId, + msgUserAuthRequest, + req.User, + req.Service, + req.Method, + true, + algo, + pubKey, + } + return Marshal(data) +} + +func appendU16(buf []byte, n uint16) []byte { + return append(buf, byte(n>>8), byte(n)) +} + +func appendU32(buf []byte, n uint32) []byte { + return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendU64(buf []byte, n uint64) []byte { + return append(buf, + byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendInt(buf []byte, n int) []byte { + return appendU32(buf, uint32(n)) +} + +func appendString(buf []byte, s string) []byte { + buf = appendU32(buf, uint32(len(s))) + buf = append(buf, s...) + return buf +} + +func appendBool(buf []byte, b bool) []byte { + if b { + return append(buf, 1) + } + return append(buf, 0) +} + +// newCond is a helper to hide the fact that there is no usable zero +// value for sync.Cond. +func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) } + +// window represents the buffer available to clients +// wishing to write to a channel. +type window struct { + *sync.Cond + win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 + writeWaiters int + closed bool +} + +// add adds win to the amount of window available +// for consumers. +func (w *window) add(win uint32) bool { + // a zero sized window adjust is a noop. + if win == 0 { + return true + } + w.L.Lock() + if w.win+win < win { + w.L.Unlock() + return false + } + w.win += win + // It is unusual that multiple goroutines would be attempting to reserve + // window space, but not guaranteed. Use broadcast to notify all waiters + // that additional window is available. + w.Broadcast() + w.L.Unlock() + return true +} + +// close sets the window to closed, so all reservations fail +// immediately. +func (w *window) close() { + w.L.Lock() + w.closed = true + w.Broadcast() + w.L.Unlock() +} + +// reserve reserves win from the available window capacity. +// If no capacity remains, reserve will block. reserve may +// return less than requested. +func (w *window) reserve(win uint32) (uint32, error) { + var err error + w.L.Lock() + w.writeWaiters++ + w.Broadcast() + for w.win == 0 && !w.closed { + w.Wait() + } + w.writeWaiters-- + if w.win < win { + win = w.win + } + w.win -= win + if w.closed { + err = io.EOF + } + w.L.Unlock() + return win, err +} + +// waitWriterBlocked waits until some goroutine is blocked for further +// writes. It is used in tests only. +func (w *window) waitWriterBlocked() { + w.Cond.L.Lock() + for w.writeWaiters == 0 { + w.Cond.Wait() + } + w.Cond.L.Unlock() +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/connection.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/connection.go new file mode 100644 index 00000000..93551e24 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/connection.go @@ -0,0 +1,144 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "fmt" + "net" +) + +// OpenChannelError is returned if the other side rejects an +// OpenChannel request. +type OpenChannelError struct { + Reason RejectionReason + Message string +} + +func (e *OpenChannelError) Error() string { + return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message) +} + +// ConnMetadata holds metadata for the connection. +type ConnMetadata interface { + // User returns the user ID for this connection. + // It is empty if no authentication is used. + User() string + + // SessionID returns the sesson hash, also denoted by H. + SessionID() []byte + + // ClientVersion returns the client's version string as hashed + // into the session ID. + ClientVersion() []byte + + // ServerVersion returns the client's version string as hashed + // into the session ID. + ServerVersion() []byte + + // RemoteAddr returns the remote address for this connection. + RemoteAddr() net.Addr + + // LocalAddr returns the local address for this connection. + LocalAddr() net.Addr +} + +// Conn represents an SSH connection for both server and client roles. +// Conn is the basis for implementing an application layer, such +// as ClientConn, which implements the traditional shell access for +// clients. +type Conn interface { + ConnMetadata + + // SendRequest sends a global request, and returns the + // reply. If wantReply is true, it returns the response status + // and payload. See also RFC4254, section 4. + SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) + + // OpenChannel tries to open an channel. If the request is + // rejected, it returns *OpenChannelError. On success it returns + // the SSH Channel and a Go channel for incoming, out-of-band + // requests. The Go channel must be serviced, or the + // connection will hang. + OpenChannel(name string, data []byte) (Channel, <-chan *Request, error) + + // Close closes the underlying network connection + Close() error + + // Wait blocks until the connection has shut down, and returns the + // error causing the shutdown. + Wait() error + + // TODO(hanwen): consider exposing: + // RequestKeyChange + // Disconnect +} + +// DiscardRequests consumes and rejects all requests from the +// passed-in channel. +func DiscardRequests(in <-chan *Request) { + for req := range in { + if req.WantReply { + req.Reply(false, nil) + } + } +} + +// A connection represents an incoming connection. +type connection struct { + transport *handshakeTransport + sshConn + + // The connection protocol. + *mux +} + +func (c *connection) Close() error { + return c.sshConn.conn.Close() +} + +// sshconn provides net.Conn metadata, but disallows direct reads and +// writes. +type sshConn struct { + conn net.Conn + + user string + sessionID []byte + clientVersion []byte + serverVersion []byte +} + +func dup(src []byte) []byte { + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func (c *sshConn) User() string { + return c.user +} + +func (c *sshConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *sshConn) Close() error { + return c.conn.Close() +} + +func (c *sshConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *sshConn) SessionID() []byte { + return dup(c.sessionID) +} + +func (c *sshConn) ClientVersion() []byte { + return dup(c.clientVersion) +} + +func (c *sshConn) ServerVersion() []byte { + return dup(c.serverVersion) +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/doc.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/doc.go similarity index 87% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/doc.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/doc.go index 22ff3388..fb6402bb 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/doc.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/doc.go @@ -13,7 +13,6 @@ others. References: [PROTOCOL.certkeys]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys - [PROTOCOL.agent]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 */ -package ssh +package ssh // import "golang.org/x/crypto/ssh" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/example_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/example_test.go similarity index 68% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/example_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/example_test.go index a88a6773..22f42ecc 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/example_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/example_test.go @@ -9,17 +9,23 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" - "code.google.com/p/go.crypto/ssh/terminal" + "golang.org/x/crypto/ssh/terminal" ) -func ExampleListen() { +func ExampleNewServerConn() { // An SSH server is represented by a ServerConfig, which holds // certificate details and handles authentication of ServerConns. config := &ServerConfig{ - PasswordCallback: func(conn *ServerConn, user, pass string) bool { - return user == "testuser" && pass == "tiger" + PasswordCallback: func(c ConnMetadata, pass []byte) (*Permissions, error) { + // Should use constant-time compare (or better, salt+hash) in + // a production setting. + if c.User() == "testuser" && string(pass) == "tiger" { + return nil, nil + } + return nil, fmt.Errorf("password rejected for %q", c.User()) }, } @@ -37,50 +43,65 @@ func ExampleListen() { // Once a ServerConfig has been configured, connections can be // accepted. - listener, err := Listen("tcp", "0.0.0.0:2022", config) + listener, err := net.Listen("tcp", "0.0.0.0:2022") if err != nil { panic("failed to listen for connection") } - sConn, err := listener.Accept() + nConn, err := listener.Accept() if err != nil { panic("failed to accept incoming connection") } - if err := sConn.Handshake(); err != nil { + + // Before use, a handshake must be performed on the incoming + // net.Conn. + _, chans, reqs, err := NewServerConn(nConn, config) + if err != nil { panic("failed to handshake") } + // The incoming Request channel must be serviced. + go DiscardRequests(reqs) - // A ServerConn multiplexes several channels, which must - // themselves be Accepted. - for { - // Accept reads from the connection, demultiplexes packets - // to their corresponding channels and returns when a new - // channel request is seen. Some goroutine must always be - // calling Accept; otherwise no messages will be forwarded - // to the channels. - channel, err := sConn.Accept() - if err != nil { - panic("error from Accept") - } - + // Service the incoming Channel channel. + for newChannel := range chans { // Channels have a type, depending on the application level // protocol intended. In the case of a shell, the type is // "session" and ServerShell may be used to present a simple // terminal interface. - if channel.ChannelType() != "session" { - channel.Reject(UnknownChannelType, "unknown channel type") + if newChannel.ChannelType() != "session" { + newChannel.Reject(UnknownChannelType, "unknown channel type") continue } - channel.Accept() + channel, requests, err := newChannel.Accept() + if err != nil { + panic("could not accept channel.") + } + + // Sessions have out-of-band requests such as "shell", + // "pty-req" and "env". Here we handle only the + // "shell" request. + go func(in <-chan *Request) { + for req := range in { + ok := false + switch req.Type { + case "shell": + ok = true + if len(req.Payload) > 0 { + // We don't accept any + // commands, only the + // default shell. + ok = false + } + } + req.Reply(ok, nil) + } + }(requests) term := terminal.NewTerminal(channel, "> ") - serverTerm := &ServerTerminal{ - Term: term, - Channel: channel, - } + go func() { defer channel.Close() for { - line, err := serverTerm.ReadLine() + line, err := term.ReadLine() if err != nil { break } @@ -95,13 +116,11 @@ func ExampleDial() { // the "password" authentication method is supported. // // To authenticate with the remote server you must pass at least one - // implementation of ClientAuth via the Auth field in ClientConfig. + // implementation of AuthMethod via the Auth field in ClientConfig. config := &ClientConfig{ User: "username", - Auth: []ClientAuth{ - // ClientAuthPassword wraps a ClientPassword implementation - // in a type that implements ClientAuth. - ClientAuthPassword(password("yourpassword")), + Auth: []AuthMethod{ + Password("yourpassword"), }, } client, err := Dial("tcp", "yourserver.com:22", config) @@ -127,11 +146,11 @@ func ExampleDial() { fmt.Println(b.String()) } -func ExampleClientConn_Listen() { +func ExampleClient_Listen() { config := &ClientConfig{ User: "username", - Auth: []ClientAuth{ - ClientAuthPassword(password("password")), + Auth: []AuthMethod{ + Password("password"), }, } // Dial your ssh server. @@ -158,8 +177,8 @@ func ExampleSession_RequestPty() { // Create client config config := &ClientConfig{ User: "username", - Auth: []ClientAuth{ - ClientAuthPassword(password("password")), + Auth: []AuthMethod{ + Password("password"), }, } // Connect to ssh server diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake.go new file mode 100644 index 00000000..a1e2c23d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake.go @@ -0,0 +1,393 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/rand" + "errors" + "fmt" + "io" + "log" + "net" + "sync" +) + +// debugHandshake, if set, prints messages sent and received. Key +// exchange messages are printed as if DH were used, so the debug +// messages are wrong when using ECDH. +const debugHandshake = false + +// keyingTransport is a packet based transport that supports key +// changes. It need not be thread-safe. It should pass through +// msgNewKeys in both directions. +type keyingTransport interface { + packetConn + + // prepareKeyChange sets up a key change. The key change for a + // direction will be effected if a msgNewKeys message is sent + // or received. + prepareKeyChange(*algorithms, *kexResult) error + + // getSessionID returns the session ID. prepareKeyChange must + // have been called once. + getSessionID() []byte +} + +// rekeyingTransport is the interface of handshakeTransport that we +// (internally) expose to ClientConn and ServerConn. +type rekeyingTransport interface { + packetConn + + // requestKeyChange asks the remote side to change keys. All + // writes are blocked until the key change succeeds, which is + // signaled by reading a msgNewKeys. + requestKeyChange() error + + // getSessionID returns the session ID. This is only valid + // after the first key change has completed. + getSessionID() []byte +} + +// handshakeTransport implements rekeying on top of a keyingTransport +// and offers a thread-safe writePacket() interface. +type handshakeTransport struct { + conn keyingTransport + config *Config + + serverVersion []byte + clientVersion []byte + + hostKeys []Signer // If hostKeys are given, we are the server. + + // On read error, incoming is closed, and readError is set. + incoming chan []byte + readError error + + // data for host key checking + hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + dialAddress string + remoteAddr net.Addr + + readSinceKex uint64 + + // Protects the writing side of the connection + mu sync.Mutex + cond *sync.Cond + sentInitPacket []byte + sentInitMsg *kexInitMsg + writtenSinceKex uint64 + writeError error +} + +func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport { + t := &handshakeTransport{ + conn: conn, + serverVersion: serverVersion, + clientVersion: clientVersion, + incoming: make(chan []byte, 16), + config: config, + } + t.cond = sync.NewCond(&t.mu) + return t +} + +func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.dialAddress = dialAddr + t.remoteAddr = addr + t.hostKeyCallback = config.HostKeyCallback + go t.readLoop() + return t +} + +func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.hostKeys = config.hostKeys + go t.readLoop() + return t +} + +func (t *handshakeTransport) getSessionID() []byte { + return t.conn.getSessionID() +} + +func (t *handshakeTransport) id() string { + if len(t.hostKeys) > 0 { + return "server" + } + return "client" +} + +func (t *handshakeTransport) readPacket() ([]byte, error) { + p, ok := <-t.incoming + if !ok { + return nil, t.readError + } + return p, nil +} + +func (t *handshakeTransport) readLoop() { + for { + p, err := t.readOnePacket() + if err != nil { + t.readError = err + close(t.incoming) + break + } + if p[0] == msgIgnore || p[0] == msgDebug { + continue + } + t.incoming <- p + } +} + +func (t *handshakeTransport) readOnePacket() ([]byte, error) { + if t.readSinceKex > t.config.RekeyThreshold { + if err := t.requestKeyChange(); err != nil { + return nil, err + } + } + + p, err := t.conn.readPacket() + if err != nil { + return nil, err + } + + t.readSinceKex += uint64(len(p)) + if debugHandshake { + msg, err := decode(p) + log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err) + } + if p[0] != msgKexInit { + return p, nil + } + err = t.enterKeyExchange(p) + + t.mu.Lock() + if err != nil { + // drop connection + t.conn.Close() + t.writeError = err + } + + if debugHandshake { + log.Printf("%s exited key exchange, err %v", t.id(), err) + } + + // Unblock writers. + t.sentInitMsg = nil + t.sentInitPacket = nil + t.cond.Broadcast() + t.writtenSinceKex = 0 + t.mu.Unlock() + + if err != nil { + return nil, err + } + + t.readSinceKex = 0 + return []byte{msgNewKeys}, nil +} + +// sendKexInit sends a key change message, and returns the message +// that was sent. After initiating the key change, all writes will be +// blocked until the change is done, and a failed key change will +// close the underlying transport. This function is safe for +// concurrent use by multiple goroutines. +func (t *handshakeTransport) sendKexInit() (*kexInitMsg, []byte, error) { + t.mu.Lock() + defer t.mu.Unlock() + return t.sendKexInitLocked() +} + +func (t *handshakeTransport) requestKeyChange() error { + _, _, err := t.sendKexInit() + return err +} + +// sendKexInitLocked sends a key change message. t.mu must be locked +// while this happens. +func (t *handshakeTransport) sendKexInitLocked() (*kexInitMsg, []byte, error) { + // kexInits may be sent either in response to the other side, + // or because our side wants to initiate a key change, so we + // may have already sent a kexInit. In that case, don't send a + // second kexInit. + if t.sentInitMsg != nil { + return t.sentInitMsg, t.sentInitPacket, nil + } + msg := &kexInitMsg{ + KexAlgos: t.config.KeyExchanges, + CiphersClientServer: t.config.Ciphers, + CiphersServerClient: t.config.Ciphers, + MACsClientServer: t.config.MACs, + MACsServerClient: t.config.MACs, + CompressionClientServer: supportedCompressions, + CompressionServerClient: supportedCompressions, + } + io.ReadFull(rand.Reader, msg.Cookie[:]) + + if len(t.hostKeys) > 0 { + for _, k := range t.hostKeys { + msg.ServerHostKeyAlgos = append( + msg.ServerHostKeyAlgos, k.PublicKey().Type()) + } + } else { + msg.ServerHostKeyAlgos = supportedHostKeyAlgos + } + packet := Marshal(msg) + + // writePacket destroys the contents, so save a copy. + packetCopy := make([]byte, len(packet)) + copy(packetCopy, packet) + + if err := t.conn.writePacket(packetCopy); err != nil { + return nil, nil, err + } + + t.sentInitMsg = msg + t.sentInitPacket = packet + return msg, packet, nil +} + +func (t *handshakeTransport) writePacket(p []byte) error { + t.mu.Lock() + if t.writtenSinceKex > t.config.RekeyThreshold { + t.sendKexInitLocked() + } + for t.sentInitMsg != nil { + t.cond.Wait() + } + if t.writeError != nil { + return t.writeError + } + t.writtenSinceKex += uint64(len(p)) + + var err error + switch p[0] { + case msgKexInit: + err = errors.New("ssh: only handshakeTransport can send kexInit") + case msgNewKeys: + err = errors.New("ssh: only handshakeTransport can send newKeys") + default: + err = t.conn.writePacket(p) + } + t.mu.Unlock() + return err +} + +func (t *handshakeTransport) Close() error { + return t.conn.Close() +} + +// enterKeyExchange runs the key exchange. +func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { + if debugHandshake { + log.Printf("%s entered key exchange", t.id()) + } + myInit, myInitPacket, err := t.sendKexInit() + if err != nil { + return err + } + + otherInit := &kexInitMsg{} + if err := Unmarshal(otherInitPacket, otherInit); err != nil { + return err + } + + magics := handshakeMagics{ + clientVersion: t.clientVersion, + serverVersion: t.serverVersion, + clientKexInit: otherInitPacket, + serverKexInit: myInitPacket, + } + + clientInit := otherInit + serverInit := myInit + if len(t.hostKeys) == 0 { + clientInit = myInit + serverInit = otherInit + + magics.clientKexInit = myInitPacket + magics.serverKexInit = otherInitPacket + } + + algs := findAgreedAlgorithms(clientInit, serverInit) + if algs == nil { + return errors.New("ssh: no common algorithms") + } + + // We don't send FirstKexFollows, but we handle receiving it. + if otherInit.FirstKexFollows && algs.kex != otherInit.KexAlgos[0] { + // other side sent a kex message for the wrong algorithm, + // which we have to ignore. + if _, err := t.conn.readPacket(); err != nil { + return err + } + } + + kex, ok := kexAlgoMap[algs.kex] + if !ok { + return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex) + } + + var result *kexResult + if len(t.hostKeys) > 0 { + result, err = t.server(kex, algs, &magics) + } else { + result, err = t.client(kex, algs, &magics) + } + + if err != nil { + return err + } + + t.conn.prepareKeyChange(algs, result) + if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { + return err + } + if packet, err := t.conn.readPacket(); err != nil { + return err + } else if packet[0] != msgNewKeys { + return unexpectedMessageError(msgNewKeys, packet[0]) + } + return nil +} + +func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + var hostKey Signer + for _, k := range t.hostKeys { + if algs.hostKey == k.PublicKey().Type() { + hostKey = k + } + } + + r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey) + return r, err +} + +func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + result, err := kex.Client(t.conn, t.config.Rand, magics) + if err != nil { + return nil, err + } + + hostKey, err := ParsePublicKey(result.HostKey) + if err != nil { + return nil, err + } + + if err := verifyHostKeySignature(hostKey, result); err != nil { + return nil, err + } + + if t.hostKeyCallback != nil { + err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) + if err != nil { + return nil, err + } + } + + return result, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake_test.go new file mode 100644 index 00000000..613c4982 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/handshake_test.go @@ -0,0 +1,311 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "fmt" + "net" + "testing" +) + +type testChecker struct { + calls []string +} + +func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { + if dialAddr == "bad" { + return fmt.Errorf("dialAddr is bad") + } + + if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil { + return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr) + } + + t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal())) + + return nil +} + +// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and +// therefore is buffered (net.Pipe deadlocks if both sides start with +// a write.) +func netPipe() (net.Conn, net.Conn, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, nil, err + } + defer listener.Close() + c1, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + return nil, nil, err + } + + c2, err := listener.Accept() + if err != nil { + c1.Close() + return nil, nil, err + } + + return c1, c2, nil +} + +func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTransport, server *handshakeTransport, err error) { + a, b, err := netPipe() + if err != nil { + return nil, nil, err + } + + trC := newTransport(a, rand.Reader, true) + trS := newTransport(b, rand.Reader, false) + clientConf.SetDefaults() + + v := []byte("version") + client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr()) + + serverConf := &ServerConfig{} + serverConf.AddHostKey(testSigners["ecdsa"]) + serverConf.SetDefaults() + server = newServerTransport(trS, v, v, serverConf) + + return client, server, nil +} + +func TestHandshakeBasic(t *testing.T) { + checker := &testChecker{} + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + + defer trC.Close() + defer trS.Close() + + go func() { + // Client writes a bunch of stuff, and does a key + // change in the middle. This should not confuse the + // handshake in progress + for i := 0; i < 10; i++ { + p := []byte{msgRequestSuccess, byte(i)} + if err := trC.writePacket(p); err != nil { + t.Fatalf("sendPacket: %v", err) + } + if i == 5 { + // halfway through, we request a key change. + _, _, err := trC.sendKexInit() + if err != nil { + t.Fatalf("sendKexInit: %v", err) + } + } + } + trC.Close() + }() + + // Server checks that client messages come in cleanly + i := 0 + for { + p, err := trS.readPacket() + if err != nil { + break + } + if p[0] == msgNewKeys { + continue + } + want := []byte{msgRequestSuccess, byte(i)} + if bytes.Compare(p, want) != 0 { + t.Errorf("message %d: got %q, want %q", i, p, want) + } + i++ + } + if i != 10 { + t.Errorf("received %d messages, want 10.", i) + } + + // If all went well, we registered exactly 1 key change. + if len(checker.calls) != 1 { + t.Fatalf("got %d host key checks, want 1", len(checker.calls)) + } + + pub := testSigners["ecdsa"].PublicKey() + want := fmt.Sprintf("%s %v %s %x", "addr", trC.remoteAddr, pub.Type(), pub.Marshal()) + if want != checker.calls[0] { + t.Errorf("got %q want %q for host key check", checker.calls[0], want) + } +} + +func TestHandshakeError(t *testing.T) { + checker := &testChecker{} + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "bad") + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + // send a packet + packet := []byte{msgRequestSuccess, 42} + if err := trC.writePacket(packet); err != nil { + t.Errorf("writePacket: %v", err) + } + + // Now request a key change. + _, _, err = trC.sendKexInit() + if err != nil { + t.Errorf("sendKexInit: %v", err) + } + + // the key change will fail, and afterwards we can't write. + if err := trC.writePacket([]byte{msgRequestSuccess, 43}); err == nil { + t.Errorf("writePacket after botched rekey succeeded.") + } + + readback, err := trS.readPacket() + if err != nil { + t.Fatalf("server closed too soon: %v", err) + } + if bytes.Compare(readback, packet) != 0 { + t.Errorf("got %q want %q", readback, packet) + } + readback, err = trS.readPacket() + if err == nil { + t.Errorf("got a message %q after failed key change", readback) + } +} + +func TestHandshakeTwice(t *testing.T) { + checker := &testChecker{} + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + + defer trC.Close() + defer trS.Close() + + // send a packet + packet := make([]byte, 5) + packet[0] = msgRequestSuccess + if err := trC.writePacket(packet); err != nil { + t.Errorf("writePacket: %v", err) + } + + // Now request a key change. + _, _, err = trC.sendKexInit() + if err != nil { + t.Errorf("sendKexInit: %v", err) + } + + // Send another packet. Use a fresh one, since writePacket destroys. + packet = make([]byte, 5) + packet[0] = msgRequestSuccess + if err := trC.writePacket(packet); err != nil { + t.Errorf("writePacket: %v", err) + } + + // 2nd key change. + _, _, err = trC.sendKexInit() + if err != nil { + t.Errorf("sendKexInit: %v", err) + } + + packet = make([]byte, 5) + packet[0] = msgRequestSuccess + if err := trC.writePacket(packet); err != nil { + t.Errorf("writePacket: %v", err) + } + + packet = make([]byte, 5) + packet[0] = msgRequestSuccess + for i := 0; i < 5; i++ { + msg, err := trS.readPacket() + if err != nil { + t.Fatalf("server closed too soon: %v", err) + } + if msg[0] == msgNewKeys { + continue + } + + if bytes.Compare(msg, packet) != 0 { + t.Errorf("packet %d: got %q want %q", i, msg, packet) + } + } + if len(checker.calls) != 2 { + t.Errorf("got %d key changes, want 2", len(checker.calls)) + } +} + +func TestHandshakeAutoRekeyWrite(t *testing.T) { + checker := &testChecker{} + clientConf := &ClientConfig{HostKeyCallback: checker.Check} + clientConf.RekeyThreshold = 500 + trC, trS, err := handshakePair(clientConf, "addr") + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + for i := 0; i < 5; i++ { + packet := make([]byte, 251) + packet[0] = msgRequestSuccess + if err := trC.writePacket(packet); err != nil { + t.Errorf("writePacket: %v", err) + } + } + + j := 0 + for ; j < 5; j++ { + _, err := trS.readPacket() + if err != nil { + break + } + } + + if j != 5 { + t.Errorf("got %d, want 5 messages", j) + } + + if len(checker.calls) != 2 { + t.Errorf("got %d key changes, wanted 2", len(checker.calls)) + } +} + +type syncChecker struct { + called chan int +} + +func (t *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { + t.called <- 1 + return nil +} + +func TestHandshakeAutoRekeyRead(t *testing.T) { + sync := &syncChecker{make(chan int, 2)} + clientConf := &ClientConfig{ + HostKeyCallback: sync.Check, + } + clientConf.RekeyThreshold = 500 + + trC, trS, err := handshakePair(clientConf, "addr") + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + packet := make([]byte, 501) + packet[0] = msgRequestSuccess + if err := trS.writePacket(packet); err != nil { + t.Fatalf("writePacket: %v", err) + } + // While we read out the packet, a key change will be + // initiated. + if _, err := trC.readPacket(); err != nil { + t.Fatalf("readPacket(client): %v", err) + } + + <-sync.called +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex.go similarity index 93% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex.go index d2e3b707..6a835c76 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex.go @@ -30,10 +30,10 @@ type kexResult struct { // Shared secret. See also RFC 4253, section 8. K []byte - // Host key as hashed into H + // Host key as hashed into H. HostKey []byte - // Signature of H + // Signature of H. Signature []byte // A cryptographic hash function that matches the security @@ -94,7 +94,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha kexDHInit := kexDHInitMsg{ X: X, } - if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil { + if err := c.writePacket(Marshal(&kexDHInit)); err != nil { return nil, err } @@ -104,7 +104,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha } var kexDHReply kexDHReplyMsg - if err = unmarshal(&kexDHReply, packet, msgKexDHReply); err != nil { + if err = Unmarshal(packet, &kexDHReply); err != nil { return nil, err } @@ -138,7 +138,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha return } var kexDHInit kexDHInitMsg - if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil { + if err = Unmarshal(packet, &kexDHInit); err != nil { return } @@ -153,7 +153,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha return nil, err } - hostKeyBytes := MarshalPublicKey(priv.PublicKey()) + hostKeyBytes := priv.PublicKey().Marshal() h := hashFunc.New() magics.write(h) @@ -179,7 +179,7 @@ func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handsha Y: Y, Signature: sig, } - packet = marshal(msgKexDHReply, kexDHReply) + packet = Marshal(&kexDHReply) err = c.writePacket(packet) return &kexResult{ @@ -207,7 +207,7 @@ func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) ( ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y), } - serialized := marshal(msgKexECDHInit, kexInit) + serialized := Marshal(&kexInit) if err := c.writePacket(serialized); err != nil { return nil, err } @@ -218,7 +218,7 @@ func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) ( } var reply kexECDHReplyMsg - if err = unmarshal(&reply, packet, msgKexECDHReply); err != nil { + if err = Unmarshal(packet, &reply); err != nil { return nil, err } @@ -297,7 +297,7 @@ func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, p } var kexECDHInit kexECDHInitMsg - if err = unmarshal(&kexECDHInit, packet, msgKexECDHInit); err != nil { + if err = Unmarshal(packet, &kexECDHInit); err != nil { return nil, err } @@ -314,7 +314,7 @@ func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, p return nil, err } - hostKeyBytes := MarshalPublicKey(priv.PublicKey()) + hostKeyBytes := priv.PublicKey().Marshal() serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y) @@ -346,7 +346,7 @@ func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, p Signature: sig, } - serialized := marshal(msgKexECDHReply, reply) + serialized := Marshal(&reply) if err := c.writePacket(serialized); err != nil { return nil, err } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex_test.go similarity index 93% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex_test.go index 1e931a31..0db5f9be 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/kex_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/kex_test.go @@ -29,7 +29,7 @@ func TestKexes(t *testing.T) { c <- kexResultErr{r, e} }() go func() { - r, e := kex.Server(b, rand.Reader, &magics, ecdsaKey) + r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"]) s <- kexResultErr{r, e} }() diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys.go similarity index 62% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys.go index b41fefc8..e8af511e 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/keys.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys.go @@ -33,7 +33,7 @@ const ( // parsePubKey parses a public key of the given algorithm. // Use ParsePublicKey for keys with prepended algorithm. -func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, ok bool) { +func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) { switch algo { case KeyAlgoRSA: return parseRSA(in) @@ -42,15 +42,19 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, ok bool case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: return parseECDSA(in) case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: - return parseOpenSSHCertV01(in, algo) + cert, err := parseCert(in, certToPrivAlgo(algo)) + if err != nil { + return nil, nil, err + } + return cert, nil, nil } - return nil, nil, false + return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", err) } // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format // (see sshd(8) manual page) once the options and key type fields have been // removed. -func parseAuthorizedKey(in []byte) (out PublicKey, comment string, ok bool) { +func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) { in = bytes.TrimSpace(in) i := bytes.IndexAny(in, " \t") @@ -62,20 +66,20 @@ func parseAuthorizedKey(in []byte) (out PublicKey, comment string, ok bool) { key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) n, err := base64.StdEncoding.Decode(key, base64Key) if err != nil { - return + return nil, "", err } key = key[:n] - out, _, ok = ParsePublicKey(key) - if !ok { - return nil, "", false + out, err = ParsePublicKey(key) + if err != nil { + return nil, "", err } comment = string(bytes.TrimSpace(in[i:])) - return + return out, comment, nil } // ParseAuthorizedKeys parses a public key from an authorized_keys // file used in OpenSSH according to the sshd(8) manual page. -func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, ok bool) { +func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { for len(in) > 0 { end := bytes.IndexByte(in, '\n') if end != -1 { @@ -102,8 +106,8 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str continue } - if out, comment, ok = parseAuthorizedKey(in[i:]); ok { - return + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + return out, comment, options, rest, nil } // No key type recognised. Maybe there's an options field at @@ -143,38 +147,42 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str continue } - if out, comment, ok = parseAuthorizedKey(in[i:]); ok { + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { options = candidateOptions - return + return out, comment, options, rest, nil } in = rest continue } - return + return nil, "", nil, nil, errors.New("ssh: no key found") } // ParsePublicKey parses an SSH public key formatted for use in // the SSH wire protocol according to RFC 4253, section 6.6. -func ParsePublicKey(in []byte) (out PublicKey, rest []byte, ok bool) { +func ParsePublicKey(in []byte) (out PublicKey, err error) { algo, in, ok := parseString(in) if !ok { - return + return nil, errShortRead + } + var rest []byte + out, rest, err = parsePubKey(in, string(algo)) + if len(rest) > 0 { + return nil, errors.New("ssh: trailing junk in public key") } - return parsePubKey(in, string(algo)) + return out, err } -// MarshalAuthorizedKey returns a byte stream suitable for inclusion -// in an OpenSSH authorized_keys file following the format specified -// in the sshd(8) manual page. +// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH +// authorized_keys file. The return value ends with newline. func MarshalAuthorizedKey(key PublicKey) []byte { b := &bytes.Buffer{} - b.WriteString(key.PublicKeyAlgo()) + b.WriteString(key.Type()) b.WriteByte(' ') e := base64.NewEncoder(base64.StdEncoding, b) - e.Write(MarshalPublicKey(key)) + e.Write(key.Marshal()) e.Close() b.WriteByte('\n') return b.Bytes() @@ -182,84 +190,81 @@ func MarshalAuthorizedKey(key PublicKey) []byte { // PublicKey is an abstraction of different types of public keys. type PublicKey interface { - // PrivateKeyAlgo returns the name of the encryption system. - PrivateKeyAlgo() string - - // PublicKeyAlgo returns the algorithm for the public key, - // which may be different from PrivateKeyAlgo for certificates. - PublicKeyAlgo() string + // Type returns the key's type, e.g. "ssh-rsa". + Type() string // Marshal returns the serialized key data in SSH wire format, - // without the name prefix. Callers should typically use - // MarshalPublicKey(). + // with the name prefix. Marshal() []byte // Verify that sig is a signature on the given data using this // key. This function will hash the data appropriately first. - Verify(data []byte, sigBlob []byte) bool + Verify(data []byte, sig *Signature) error } -// A Signer is can create signatures that verify against a public key. +// A Signer can create signatures that verify against a public key. type Signer interface { // PublicKey returns an associated PublicKey instance. PublicKey() PublicKey // Sign returns raw signature for the given data. This method // will apply the hash specified for the keytype to the data. - Sign(rand io.Reader, data []byte) ([]byte, error) + Sign(rand io.Reader, data []byte) (*Signature, error) } type rsaPublicKey rsa.PublicKey -func (r *rsaPublicKey) PrivateKeyAlgo() string { +func (r *rsaPublicKey) Type() string { return "ssh-rsa" } -func (r *rsaPublicKey) PublicKeyAlgo() string { - return r.PrivateKeyAlgo() -} - // parseRSA parses an RSA key according to RFC 4253, section 6.6. -func parseRSA(in []byte) (out PublicKey, rest []byte, ok bool) { - key := new(rsa.PublicKey) - - bigE, in, ok := parseInt(in) - if !ok || bigE.BitLen() > 24 { - return +func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + E *big.Int + N *big.Int + Rest []byte `ssh:"rest"` } - e := bigE.Int64() + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if w.E.BitLen() > 24 { + return nil, nil, errors.New("ssh: exponent too large") + } + e := w.E.Int64() if e < 3 || e&1 == 0 { - ok = false - return + return nil, nil, errors.New("ssh: incorrect exponent") } + + var key rsa.PublicKey key.E = int(e) - - if key.N, in, ok = parseInt(in); !ok { - return - } - - ok = true - return (*rsaPublicKey)(key), in, ok + key.N = w.N + return (*rsaPublicKey)(&key), w.Rest, nil } func (r *rsaPublicKey) Marshal() []byte { - // See RFC 4253, section 6.6. e := new(big.Int).SetInt64(int64(r.E)) - length := intLength(e) - length += intLength(r.N) - - ret := make([]byte, length) - rest := marshalInt(ret, e) - marshalInt(rest, r.N) - - return ret + wirekey := struct { + Name string + E *big.Int + N *big.Int + }{ + KeyAlgoRSA, + e, + r.N, + } + return Marshal(&wirekey) } -func (r *rsaPublicKey) Verify(data []byte, sig []byte) bool { +func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != r.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type()) + } h := crypto.SHA1.New() h.Write(data) digest := h.Sum(nil) - return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig) == nil + return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob) } type rsaPrivateKey struct { @@ -270,64 +275,66 @@ func (r *rsaPrivateKey) PublicKey() PublicKey { return (*rsaPublicKey)(&r.PrivateKey.PublicKey) } -func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) { +func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { h := crypto.SHA1.New() h.Write(data) digest := h.Sum(nil) - return rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest) + blob, err := rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest) + if err != nil { + return nil, err + } + return &Signature{ + Format: r.PublicKey().Type(), + Blob: blob, + }, nil } type dsaPublicKey dsa.PublicKey -func (r *dsaPublicKey) PrivateKeyAlgo() string { +func (r *dsaPublicKey) Type() string { return "ssh-dss" } -func (r *dsaPublicKey) PublicKeyAlgo() string { - return r.PrivateKeyAlgo() -} - // parseDSA parses an DSA key according to RFC 4253, section 6.6. -func parseDSA(in []byte) (out PublicKey, rest []byte, ok bool) { - key := new(dsa.PublicKey) - - if key.P, in, ok = parseInt(in); !ok { - return +func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + P, Q, G, Y *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err } - if key.Q, in, ok = parseInt(in); !ok { - return + key := &dsaPublicKey{ + Parameters: dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + }, + Y: w.Y, } - - if key.G, in, ok = parseInt(in); !ok { - return - } - - if key.Y, in, ok = parseInt(in); !ok { - return - } - - ok = true - return (*dsaPublicKey)(key), in, ok + return key, w.Rest, nil } -func (r *dsaPublicKey) Marshal() []byte { - // See RFC 4253, section 6.6. - length := intLength(r.P) - length += intLength(r.Q) - length += intLength(r.G) - length += intLength(r.Y) +func (k *dsaPublicKey) Marshal() []byte { + w := struct { + Name string + P, Q, G, Y *big.Int + }{ + k.Type(), + k.P, + k.Q, + k.G, + k.Y, + } - ret := make([]byte, length) - rest := marshalInt(ret, r.P) - rest = marshalInt(rest, r.Q) - rest = marshalInt(rest, r.G) - marshalInt(rest, r.Y) - - return ret + return Marshal(&w) } -func (k *dsaPublicKey) Verify(data []byte, sigBlob []byte) bool { +func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } h := crypto.SHA1.New() h.Write(data) digest := h.Sum(nil) @@ -337,12 +344,15 @@ func (k *dsaPublicKey) Verify(data []byte, sigBlob []byte) bool { // r, followed by s (which are 160-bit integers, without lengths or // padding, unsigned, and in network byte order). // For DSS purposes, sig.Blob should be exactly 40 bytes in length. - if len(sigBlob) != 40 { - return false + if len(sig.Blob) != 40 { + return errors.New("ssh: DSA signature parse error") } - r := new(big.Int).SetBytes(sigBlob[:20]) - s := new(big.Int).SetBytes(sigBlob[20:]) - return dsa.Verify((*dsa.PublicKey)(k), digest, r, s) + r := new(big.Int).SetBytes(sig.Blob[:20]) + s := new(big.Int).SetBytes(sig.Blob[20:]) + if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) { + return nil + } + return errors.New("ssh: signature did not verify") } type dsaPrivateKey struct { @@ -353,7 +363,7 @@ func (k *dsaPrivateKey) PublicKey() PublicKey { return (*dsaPublicKey)(&k.PrivateKey.PublicKey) } -func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) { +func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { h := crypto.SHA1.New() h.Write(data) digest := h.Sum(nil) @@ -363,14 +373,21 @@ func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) { } sig := make([]byte, 40) - copy(sig[:20], r.Bytes()) - copy(sig[20:], s.Bytes()) - return sig, nil + rb := r.Bytes() + sb := s.Bytes() + + copy(sig[20-len(rb):20], rb) + copy(sig[40-len(sb):], sb) + + return &Signature{ + Format: k.PublicKey().Type(), + Blob: sig, + }, nil } type ecdsaPublicKey ecdsa.PublicKey -func (key *ecdsaPublicKey) PrivateKeyAlgo() string { +func (key *ecdsaPublicKey) Type() string { return "ecdsa-sha2-" + key.nistID() } @@ -387,7 +404,7 @@ func (key *ecdsaPublicKey) nistID() string { } func supportedEllipticCurve(curve elliptic.Curve) bool { - return (curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()) + return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() } // ecHash returns the hash to match the given elliptic curve, see RFC @@ -403,15 +420,11 @@ func ecHash(curve elliptic.Curve) crypto.Hash { return crypto.SHA512 } -func (key *ecdsaPublicKey) PublicKeyAlgo() string { - return key.PrivateKeyAlgo() -} - // parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. -func parseECDSA(in []byte) (out PublicKey, rest []byte, ok bool) { - var identifier []byte - if identifier, in, ok = parseString(in); !ok { - return +func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) { + identifier, in, ok := parseString(in) + if !ok { + return nil, nil, errShortRead } key := new(ecdsa.PublicKey) @@ -424,38 +437,42 @@ func parseECDSA(in []byte) (out PublicKey, rest []byte, ok bool) { case "nistp521": key.Curve = elliptic.P521() default: - ok = false - return + return nil, nil, errors.New("ssh: unsupported curve") } var keyBytes []byte if keyBytes, in, ok = parseString(in); !ok { - return + return nil, nil, errShortRead } key.X, key.Y = elliptic.Unmarshal(key.Curve, keyBytes) if key.X == nil || key.Y == nil { - ok = false - return + return nil, nil, errors.New("ssh: invalid curve point") } - return (*ecdsaPublicKey)(key), in, ok + return (*ecdsaPublicKey)(key), in, nil } func (key *ecdsaPublicKey) Marshal() []byte { // See RFC 5656, section 3.1. keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y) + w := struct { + Name string + ID string + Key []byte + }{ + key.Type(), + key.nistID(), + keyBytes, + } - ID := key.nistID() - length := stringLength(len(ID)) - length += stringLength(len(keyBytes)) - - ret := make([]byte, length) - r := marshalString(ret, []byte(ID)) - r = marshalString(r, keyBytes) - return ret + return Marshal(&w) } -func (key *ecdsaPublicKey) Verify(data []byte, sigBlob []byte) bool { +func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != key.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type()) + } + h := ecHash(key.Curve).New() h.Write(data) digest := h.Sum(nil) @@ -464,15 +481,19 @@ func (key *ecdsaPublicKey) Verify(data []byte, sigBlob []byte) bool { // The ecdsa_signature_blob value has the following specific encoding: // mpint r // mpint s - r, rest, ok := parseInt(sigBlob) - if !ok { - return false + var ecSig struct { + R *big.Int + S *big.Int } - s, rest, ok := parseInt(rest) - if !ok || len(rest) > 0 { - return false + + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err } - return ecdsa.Verify((*ecdsa.PublicKey)(key), digest, r, s) + + if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") } type ecdsaPrivateKey struct { @@ -483,7 +504,7 @@ func (k *ecdsaPrivateKey) PublicKey() PublicKey { return (*ecdsaPublicKey)(&k.PrivateKey.PublicKey) } -func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) { +func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { h := ecHash(k.PrivateKey.PublicKey.Curve).New() h.Write(data) digest := h.Sum(nil) @@ -495,10 +516,13 @@ func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) { sig := make([]byte, intLength(r)+intLength(s)) rest := marshalInt(sig, r) marshalInt(rest, s) - return sig, nil + return &Signature{ + Format: k.PublicKey().Type(), + Blob: sig, + }, nil } -// NewPrivateKey takes a pointer to rsa, dsa or ecdsa PrivateKey +// NewSignerFromKey takes a pointer to rsa, dsa or ecdsa PrivateKey // returns a corresponding Signer instance. EC keys should use P256, // P384 or P521. func NewSignerFromKey(k interface{}) (Signer, error) { @@ -540,54 +564,49 @@ func NewPublicKey(k interface{}) (PublicKey, error) { return sshKey, nil } -// ParsePublicKey parses a PEM encoded private key. It supports -// PKCS#1, RSA, DSA and ECDSA private keys. +// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports +// the same keys as ParseRawPrivateKey. func ParsePrivateKey(pemBytes []byte) (Signer, error) { + key, err := ParseRawPrivateKey(pemBytes) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// ParseRawPrivateKey returns a private key from a PEM encoded private key. It +// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys. +func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("ssh: no key found") } - var rawkey interface{} switch block.Type { case "RSA PRIVATE KEY": - rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - rawkey = rsa + return x509.ParsePKCS1PrivateKey(block.Bytes) case "EC PRIVATE KEY": - ec, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, err - } - rawkey = ec + return x509.ParseECPrivateKey(block.Bytes) case "DSA PRIVATE KEY": - ec, err := parseDSAPrivate(block.Bytes) - if err != nil { - return nil, err - } - rawkey = ec + return ParseDSAPrivateKey(block.Bytes) default: return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) } - - return NewSignerFromKey(rawkey) } -// parseDSAPrivate parses a DSA key in ASN.1 DER encoding, as -// documented in the OpenSSL DSA manpage. -// TODO(hanwen): move this in to crypto/x509 after the Go 1.2 freeze. -func parseDSAPrivate(p []byte) (*dsa.PrivateKey, error) { - k := struct { +// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as +// specified by the OpenSSL DSA man page. +func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { + var k struct { Version int P *big.Int Q *big.Int G *big.Int Priv *big.Int Pub *big.Int - }{} - rest, err := asn1.Unmarshal(p, &k) + } + rest, err := asn1.Unmarshal(der, &k) if err != nil { return nil, errors.New("ssh: failed to parse DSA key: " + err.Error()) } diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys_test.go new file mode 100644 index 00000000..36b97ad2 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/keys_test.go @@ -0,0 +1,306 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "fmt" + "reflect" + "strings" + "testing" + + "golang.org/x/crypto/ssh/testdata" +) + +func rawKey(pub PublicKey) interface{} { + switch k := pub.(type) { + case *rsaPublicKey: + return (*rsa.PublicKey)(k) + case *dsaPublicKey: + return (*dsa.PublicKey)(k) + case *ecdsaPublicKey: + return (*ecdsa.PublicKey)(k) + case *Certificate: + return k + } + panic("unknown key type") +} + +func TestKeyMarshalParse(t *testing.T) { + for _, priv := range testSigners { + pub := priv.PublicKey() + roundtrip, err := ParsePublicKey(pub.Marshal()) + if err != nil { + t.Errorf("ParsePublicKey(%T): %v", pub, err) + } + + k1 := rawKey(pub) + k2 := rawKey(roundtrip) + + if !reflect.DeepEqual(k1, k2) { + t.Errorf("got %#v in roundtrip, want %#v", k2, k1) + } + } +} + +func TestUnsupportedCurves(t *testing.T) { + raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + + if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P256") { + t.Fatalf("NewPrivateKey should not succeed with P224, got: %v", err) + } + + if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P256") { + t.Fatalf("NewPublicKey should not succeed with P224, got: %v", err) + } +} + +func TestNewPublicKey(t *testing.T) { + for _, k := range testSigners { + raw := rawKey(k.PublicKey()) + // Skip certificates, as NewPublicKey does not support them. + if _, ok := raw.(*Certificate); ok { + continue + } + pub, err := NewPublicKey(raw) + if err != nil { + t.Errorf("NewPublicKey(%#v): %v", raw, err) + } + if !reflect.DeepEqual(k.PublicKey(), pub) { + t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey()) + } + } +} + +func TestKeySignVerify(t *testing.T) { + for _, priv := range testSigners { + pub := priv.PublicKey() + + data := []byte("sign me") + sig, err := priv.Sign(rand.Reader, data) + if err != nil { + t.Fatalf("Sign(%T): %v", priv, err) + } + + if err := pub.Verify(data, sig); err != nil { + t.Errorf("publicKey.Verify(%T): %v", priv, err) + } + sig.Blob[5]++ + if err := pub.Verify(data, sig); err == nil { + t.Errorf("publicKey.Verify on broken sig did not fail") + } + } +} + +func TestParseRSAPrivateKey(t *testing.T) { + key := testPrivateKeys["rsa"] + + rsa, ok := key.(*rsa.PrivateKey) + if !ok { + t.Fatalf("got %T, want *rsa.PrivateKey", rsa) + } + + if err := rsa.Validate(); err != nil { + t.Errorf("Validate: %v", err) + } +} + +func TestParseECPrivateKey(t *testing.T) { + key := testPrivateKeys["ecdsa"] + + ecKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey) + } + + if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) { + t.Fatalf("public key does not validate.") + } +} + +func TestParseDSA(t *testing.T) { + // We actually exercise the ParsePrivateKey codepath here, as opposed to + // using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go + // uses. + s, err := ParsePrivateKey(testdata.PEMBytes["dsa"]) + if err != nil { + t.Fatalf("ParsePrivateKey returned error: %s", err) + } + + data := []byte("sign me") + sig, err := s.Sign(rand.Reader, data) + if err != nil { + t.Fatalf("dsa.Sign: %v", err) + } + + if err := s.PublicKey().Verify(data, sig); err != nil { + t.Errorf("Verify failed: %v", err) + } +} + +// Tests for authorized_keys parsing. + +// getTestKey returns a public key, and its base64 encoding. +func getTestKey() (PublicKey, string) { + k := testPublicKeys["rsa"] + + b := &bytes.Buffer{} + e := base64.NewEncoder(base64.StdEncoding, b) + e.Write(k.Marshal()) + e.Close() + + return k, b.String() +} + +func TestMarshalParsePublicKey(t *testing.T) { + pub, pubSerialized := getTestKey() + line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized) + + authKeys := MarshalAuthorizedKey(pub) + actualFields := strings.Fields(string(authKeys)) + if len(actualFields) == 0 { + t.Fatalf("failed authKeys: %v", authKeys) + } + + // drop the comment + expectedFields := strings.Fields(line)[0:2] + + if !reflect.DeepEqual(actualFields, expectedFields) { + t.Errorf("got %v, expected %v", actualFields, expectedFields) + } + + actPub, _, _, _, err := ParseAuthorizedKey([]byte(line)) + if err != nil { + t.Fatalf("cannot parse %v: %v", line, err) + } + if !reflect.DeepEqual(actPub, pub) { + t.Errorf("got %v, expected %v", actPub, pub) + } +} + +type authResult struct { + pubKey PublicKey + options []string + comments string + rest string + ok bool +} + +func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) { + rest := authKeys + var values []authResult + for len(rest) > 0 { + var r authResult + var err error + r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest) + r.ok = (err == nil) + t.Log(err) + r.rest = string(rest) + values = append(values, r) + } + + if !reflect.DeepEqual(values, expected) { + t.Errorf("got %#v, expected %#v", values, expected) + } +} + +func TestAuthorizedKeyBasic(t *testing.T) { + pub, pubSerialized := getTestKey() + line := "ssh-rsa " + pubSerialized + " user@host" + testAuthorizedKeys(t, []byte(line), + []authResult{ + {pub, nil, "user@host", "", true}, + }) +} + +func TestAuth(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithOptions := []string{ + `# comments to ignore before any keys...`, + ``, + `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`, + `# comments to ignore, along with a blank line`, + ``, + `env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`, + ``, + `# more comments, plus a invalid entry`, + `ssh-rsa data-that-will-not-parse user@host3`, + } + for _, eol := range []string{"\n", "\r\n"} { + authOptions := strings.Join(authWithOptions, eol) + rest2 := strings.Join(authWithOptions[3:], eol) + rest3 := strings.Join(authWithOptions[6:], eol) + testAuthorizedKeys(t, []byte(authOptions), []authResult{ + {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, + {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, + {nil, nil, "", "", false}, + }) + } +} + +func TestAuthWithQuotedSpaceInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{ + {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) +} + +func TestAuthWithQuotedCommaInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{ + {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) +} + +func TestAuthWithQuotedQuoteInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`) + authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{ + {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) + + testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{ + {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true}, + }) +} + +func TestAuthWithInvalidSpace(t *testing.T) { + _, pubSerialized := getTestKey() + authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host +#more to follow but still no valid keys`) + testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{ + {nil, nil, "", "", false}, + }) +} + +func TestAuthWithMissingQuote(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host +env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`) + + testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{ + {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true}, + }) +} + +func TestInvalidEntry(t *testing.T) { + authInvalid := []byte(`ssh-rsa`) + _, _, _, _, err := ParseAuthorizedKey(authInvalid) + if err == nil { + t.Errorf("got valid entry for %q", authInvalid) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mac.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mac.go similarity index 80% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mac.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mac.go index 6862d3e3..aff40429 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mac.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mac.go @@ -43,11 +43,6 @@ func (t truncatingMAC) Size() int { func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } -// Specifies a default set of MAC algorithms and a preference order. -// This is based on RFC 4253, section 6.4, with the removal of the -// hmac-md5 variants as they have reached the end of their useful life. -var DefaultMACOrder = []string{"hmac-sha1", "hmac-sha1-96"} - var macModes = map[string]*macMode{ "hmac-sha1": {20, func(key []byte) hash.Hash { return hmac.New(sha1.New, key) diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mempipe_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mempipe_test.go similarity index 87% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mempipe_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mempipe_test.go index ec1b854e..92519dd6 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/mempipe_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mempipe_test.go @@ -36,24 +36,32 @@ func (t *memTransport) readPacket() ([]byte, error) { } } -func (t *memTransport) Close() error { - t.write.Lock() - defer t.write.Unlock() - if t.write.eof { +func (t *memTransport) closeSelf() error { + t.Lock() + defer t.Unlock() + if t.eof { return io.EOF } - t.write.eof = true - t.write.Cond.Broadcast() + t.eof = true + t.Cond.Broadcast() return nil } +func (t *memTransport) Close() error { + err := t.write.closeSelf() + t.closeSelf() + return err +} + func (t *memTransport) writePacket(p []byte) error { t.write.Lock() defer t.write.Unlock() if t.write.eof { return io.EOF } - t.write.pending = append(t.write.pending, p) + c := make([]byte, len(p)) + copy(c, p) + t.write.pending = append(t.write.pending, c) t.write.Cond.Signal() return nil } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages.go similarity index 67% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages.go index 94c3ea03..f9e44bb1 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages.go @@ -7,58 +7,25 @@ package ssh import ( "bytes" "encoding/binary" + "errors" + "fmt" "io" "math/big" "reflect" + "strconv" ) // These are SSH message type numbers. They are scattered around several // documents but many were taken from [SSH-PARAMETERS]. const ( - msgDisconnect = 1 - msgIgnore = 2 - msgUnimplemented = 3 - msgDebug = 4 - msgServiceRequest = 5 - msgServiceAccept = 6 - - msgKexInit = 20 - msgNewKeys = 21 - - // Diffie-Helman - msgKexDHInit = 30 - msgKexDHReply = 31 - - msgKexECDHInit = 30 - msgKexECDHReply = 31 + msgIgnore = 2 + msgUnimplemented = 3 + msgDebug = 4 + msgNewKeys = 21 // Standard authentication messages - msgUserAuthRequest = 50 - msgUserAuthFailure = 51 - msgUserAuthSuccess = 52 - msgUserAuthBanner = 53 - msgUserAuthPubKeyOk = 60 - - // Method specific messages - msgUserAuthInfoRequest = 60 - msgUserAuthInfoResponse = 61 - - msgGlobalRequest = 80 - msgRequestSuccess = 81 - msgRequestFailure = 82 - - // Channel manipulation - msgChannelOpen = 90 - msgChannelOpenConfirm = 91 - msgChannelOpenFailure = 92 - msgChannelWindowAdjust = 93 - msgChannelData = 94 - msgChannelExtendedData = 95 - msgChannelEOF = 96 - msgChannelClose = 97 - msgChannelRequest = 98 - msgChannelSuccess = 99 - msgChannelFailure = 100 + msgUserAuthSuccess = 52 + msgUserAuthBanner = 53 ) // SSH messages: @@ -69,15 +36,25 @@ const ( // ssh tag of "rest" receives the remainder of a packet when unmarshaling. // See RFC 4253, section 11.1. +const msgDisconnect = 1 + +// disconnectMsg is the message that signals a disconnect. It is also +// the error type returned from mux.Wait() type disconnectMsg struct { - Reason uint32 + Reason uint32 `sshtype:"1"` Message string Language string } +func (d *disconnectMsg) Error() string { + return fmt.Sprintf("ssh: disconnect reason %d: %s", d.Reason, d.Message) +} + // See RFC 4253, section 7.1. +const msgKexInit = 20 + type kexInitMsg struct { - Cookie [16]byte + Cookie [16]byte `sshtype:"20"` KexAlgos []string ServerHostKeyAlgos []string CiphersClientServer []string @@ -93,53 +70,74 @@ type kexInitMsg struct { } // See RFC 4253, section 8. + +// Diffie-Helman +const msgKexDHInit = 30 + type kexDHInitMsg struct { - X *big.Int + X *big.Int `sshtype:"30"` } +const msgKexECDHInit = 30 + type kexECDHInitMsg struct { - ClientPubKey []byte + ClientPubKey []byte `sshtype:"30"` } +const msgKexECDHReply = 31 + type kexECDHReplyMsg struct { - HostKey []byte + HostKey []byte `sshtype:"31"` EphemeralPubKey []byte Signature []byte } +const msgKexDHReply = 31 + type kexDHReplyMsg struct { - HostKey []byte + HostKey []byte `sshtype:"31"` Y *big.Int Signature []byte } // See RFC 4253, section 10. +const msgServiceRequest = 5 + type serviceRequestMsg struct { - Service string + Service string `sshtype:"5"` } // See RFC 4253, section 10. +const msgServiceAccept = 6 + type serviceAcceptMsg struct { - Service string + Service string `sshtype:"6"` } // See RFC 4252, section 5. +const msgUserAuthRequest = 50 + type userAuthRequestMsg struct { - User string + User string `sshtype:"50"` Service string Method string Payload []byte `ssh:"rest"` } // See RFC 4252, section 5.1 +const msgUserAuthFailure = 51 + type userAuthFailureMsg struct { - Methods []string + Methods []string `sshtype:"51"` PartialSuccess bool } // See RFC 4256, section 3.2 +const msgUserAuthInfoRequest = 60 +const msgUserAuthInfoResponse = 61 + type userAuthInfoRequestMsg struct { - User string + User string `sshtype:"60"` Instruction string DeprecatedLanguage string NumPrompts uint32 @@ -147,17 +145,24 @@ type userAuthInfoRequestMsg struct { } // See RFC 4254, section 5.1. +const msgChannelOpen = 90 + type channelOpenMsg struct { - ChanType string + ChanType string `sshtype:"90"` PeersId uint32 PeersWindow uint32 MaxPacketSize uint32 TypeSpecificData []byte `ssh:"rest"` } +const msgChannelExtendedData = 95 +const msgChannelData = 94 + // See RFC 4254, section 5.1. +const msgChannelOpenConfirm = 91 + type channelOpenConfirmMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"91"` MyId uint32 MyWindow uint32 MaxPacketSize uint32 @@ -165,172 +170,239 @@ type channelOpenConfirmMsg struct { } // See RFC 4254, section 5.1. +const msgChannelOpenFailure = 92 + type channelOpenFailureMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"92"` Reason RejectionReason Message string Language string } +const msgChannelRequest = 98 + type channelRequestMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"98"` Request string WantReply bool RequestSpecificData []byte `ssh:"rest"` } // See RFC 4254, section 5.4. +const msgChannelSuccess = 99 + type channelRequestSuccessMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"99"` } // See RFC 4254, section 5.4. +const msgChannelFailure = 100 + type channelRequestFailureMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"100"` } // See RFC 4254, section 5.3 +const msgChannelClose = 97 + type channelCloseMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"97"` } // See RFC 4254, section 5.3 +const msgChannelEOF = 96 + type channelEOFMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"96"` } // See RFC 4254, section 4 +const msgGlobalRequest = 80 + type globalRequestMsg struct { - Type string + Type string `sshtype:"80"` WantReply bool + Data []byte `ssh:"rest"` } // See RFC 4254, section 4 +const msgRequestSuccess = 81 + type globalRequestSuccessMsg struct { - Data []byte `ssh:"rest"` + Data []byte `ssh:"rest" sshtype:"81"` } // See RFC 4254, section 4 +const msgRequestFailure = 82 + type globalRequestFailureMsg struct { - Data []byte `ssh:"rest"` + Data []byte `ssh:"rest" sshtype:"82"` } // See RFC 4254, section 5.2 +const msgChannelWindowAdjust = 93 + type windowAdjustMsg struct { - PeersId uint32 + PeersId uint32 `sshtype:"93"` AdditionalBytes uint32 } // See RFC 4252, section 7 +const msgUserAuthPubKeyOk = 60 + type userAuthPubKeyOkMsg struct { - Algo string - PubKey string + Algo string `sshtype:"60"` + PubKey []byte } -// unmarshal parses the SSH wire data in packet into out using -// reflection. expectedType, if non-zero, is the SSH message type that -// the packet is expected to start with. unmarshal either returns nil -// on success, or a ParseError or UnexpectedMessageError on error. -func unmarshal(out interface{}, packet []byte, expectedType uint8) error { - if len(packet) == 0 { - return ParseError{expectedType} - } - if expectedType > 0 { - if packet[0] != expectedType { - return UnexpectedMessageError{expectedType, packet[0]} - } - packet = packet[1:] +// typeTag returns the type byte for the given type. The type should +// be struct. +func typeTag(structType reflect.Type) byte { + var tag byte + var tagStr string + tagStr = structType.Field(0).Tag.Get("sshtype") + i, err := strconv.Atoi(tagStr) + if err == nil { + tag = byte(i) } + return tag +} +func fieldError(t reflect.Type, field int, problem string) error { + if problem != "" { + problem = ": " + problem + } + return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem) +} + +var errShortRead = errors.New("ssh: short read") + +// Unmarshal parses data in SSH wire format into a structure. The out +// argument should be a pointer to struct. If the first member of the +// struct has the "sshtype" tag set to a number in decimal, the packet +// must start that number. In case of error, Unmarshal returns a +// ParseError or UnexpectedMessageError. +func Unmarshal(data []byte, out interface{}) error { v := reflect.ValueOf(out).Elem() structType := v.Type() + expectedType := typeTag(structType) + if len(data) == 0 { + return parseError(expectedType) + } + if expectedType > 0 { + if data[0] != expectedType { + return unexpectedMessageError(expectedType, data[0]) + } + data = data[1:] + } + var ok bool for i := 0; i < v.NumField(); i++ { field := v.Field(i) t := field.Type() switch t.Kind() { case reflect.Bool: - if len(packet) < 1 { - return ParseError{expectedType} + if len(data) < 1 { + return errShortRead } - field.SetBool(packet[0] != 0) - packet = packet[1:] + field.SetBool(data[0] != 0) + data = data[1:] case reflect.Array: if t.Elem().Kind() != reflect.Uint8 { - panic("array of non-uint8") + return fieldError(structType, i, "array of unsupported type") } - if len(packet) < t.Len() { - return ParseError{expectedType} + if len(data) < t.Len() { + return errShortRead } for j, n := 0, t.Len(); j < n; j++ { - field.Index(j).Set(reflect.ValueOf(packet[j])) + field.Index(j).Set(reflect.ValueOf(data[j])) } - packet = packet[t.Len():] + data = data[t.Len():] + case reflect.Uint64: + var u64 uint64 + if u64, data, ok = parseUint64(data); !ok { + return errShortRead + } + field.SetUint(u64) case reflect.Uint32: var u32 uint32 - if u32, packet, ok = parseUint32(packet); !ok { - return ParseError{expectedType} + if u32, data, ok = parseUint32(data); !ok { + return errShortRead } field.SetUint(uint64(u32)) + case reflect.Uint8: + if len(data) < 1 { + return errShortRead + } + field.SetUint(uint64(data[0])) + data = data[1:] case reflect.String: var s []byte - if s, packet, ok = parseString(packet); !ok { - return ParseError{expectedType} + if s, data, ok = parseString(data); !ok { + return fieldError(structType, i, "") } field.SetString(string(s)) case reflect.Slice: switch t.Elem().Kind() { case reflect.Uint8: if structType.Field(i).Tag.Get("ssh") == "rest" { - field.Set(reflect.ValueOf(packet)) - packet = nil + field.Set(reflect.ValueOf(data)) + data = nil } else { var s []byte - if s, packet, ok = parseString(packet); !ok { - return ParseError{expectedType} + if s, data, ok = parseString(data); !ok { + return errShortRead } field.Set(reflect.ValueOf(s)) } case reflect.String: var nl []string - if nl, packet, ok = parseNameList(packet); !ok { - return ParseError{expectedType} + if nl, data, ok = parseNameList(data); !ok { + return errShortRead } field.Set(reflect.ValueOf(nl)) default: - panic("slice of unknown type") + return fieldError(structType, i, "slice of unsupported type") } case reflect.Ptr: if t == bigIntType { var n *big.Int - if n, packet, ok = parseInt(packet); !ok { - return ParseError{expectedType} + if n, data, ok = parseInt(data); !ok { + return errShortRead } field.Set(reflect.ValueOf(n)) } else { - panic("pointer to unknown type") + return fieldError(structType, i, "pointer to unsupported type") } default: - panic("unknown type") + return fieldError(structType, i, "unsupported type") } } - if len(packet) != 0 { - return ParseError{expectedType} + if len(data) != 0 { + return parseError(expectedType) } return nil } -// marshal serializes the message in msg. The given message type is -// prepended if it is non-zero. -func marshal(msgType uint8, msg interface{}) []byte { +// Marshal serializes the message in msg to SSH wire format. The msg +// argument should be a struct or pointer to struct. If the first +// member has the "sshtype" tag set to a number in decimal, that +// number is prepended to the result. If the last of member has the +// "ssh" tag set to "rest", its contents are appended to the output. +func Marshal(msg interface{}) []byte { out := make([]byte, 0, 64) + return marshalStruct(out, msg) +} + +func marshalStruct(out []byte, msg interface{}) []byte { + v := reflect.Indirect(reflect.ValueOf(msg)) + msgType := typeTag(v.Type()) if msgType > 0 { out = append(out, msgType) } - v := reflect.ValueOf(msg) for i, n := 0, v.NumField(); i < n; i++ { field := v.Field(i) switch t := field.Type(); t.Kind() { @@ -342,13 +414,17 @@ func marshal(msgType uint8, msg interface{}) []byte { out = append(out, v) case reflect.Array: if t.Elem().Kind() != reflect.Uint8 { - panic("array of non-uint8") + panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface())) } for j, l := 0, t.Len(); j < l; j++ { out = append(out, uint8(field.Index(j).Uint())) } case reflect.Uint32: out = appendU32(out, uint32(field.Uint())) + case reflect.Uint64: + out = appendU64(out, uint64(field.Uint())) + case reflect.Uint8: + out = append(out, uint8(field.Uint())) case reflect.String: s := field.String() out = appendInt(out, len(s)) @@ -375,7 +451,7 @@ func marshal(msgType uint8, msg interface{}) []byte { binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4)) } default: - panic("slice of unknown type") + panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface())) } case reflect.Ptr: if t == bigIntType { @@ -393,7 +469,7 @@ func marshal(msgType uint8, msg interface{}) []byte { out = out[:oldLength+needed] marshalInt(out[oldLength:], n) } else { - panic("pointer to unknown type") + panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface())) } } } @@ -477,17 +553,6 @@ func parseUint64(in []byte) (uint64, []byte, bool) { return binary.BigEndian.Uint64(in), in[8:], true } -func nameListLength(namelist []string) int { - length := 4 /* uint32 length prefix */ - for i, name := range namelist { - if i != 0 { - length++ /* comma */ - } - length += len(name) - } - return length -} - func intLength(n *big.Int) int { length := 4 /* length bytes */ if n.Sign() < 0 { @@ -650,9 +715,9 @@ func decode(packet []byte) (interface{}, error) { case msgChannelFailure: msg = new(channelRequestFailureMsg) default: - return nil, UnexpectedMessageError{0, packet[0]} + return nil, unexpectedMessageError(0, packet[0]) } - if err := unmarshal(msg, packet, packet[0]); err != nil { + if err := Unmarshal(packet, msg); err != nil { return nil, err } return msg, nil diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages_test.go similarity index 64% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages_test.go index ec1d7be6..21d52daf 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/messages_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/messages_test.go @@ -5,6 +5,7 @@ package ssh import ( + "bytes" "math/big" "math/rand" "reflect" @@ -32,48 +33,62 @@ func TestIntLength(t *testing.T) { } } -var messageTypes = []interface{}{ - &kexInitMsg{}, - &kexDHInitMsg{}, - &serviceRequestMsg{}, - &serviceAcceptMsg{}, - &userAuthRequestMsg{}, - &channelOpenMsg{}, - &channelOpenConfirmMsg{}, - &channelOpenFailureMsg{}, - &channelRequestMsg{}, - &channelRequestSuccessMsg{}, +type msgAllTypes struct { + Bool bool `sshtype:"21"` + Array [16]byte + Uint64 uint64 + Uint32 uint32 + Uint8 uint8 + String string + Strings []string + Bytes []byte + Int *big.Int + Rest []byte `ssh:"rest"` +} + +func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value { + m := &msgAllTypes{} + m.Bool = rand.Intn(2) == 1 + randomBytes(m.Array[:], rand) + m.Uint64 = uint64(rand.Int63n(1<<63 - 1)) + m.Uint32 = uint32(rand.Intn((1 << 31) - 1)) + m.Uint8 = uint8(rand.Intn(1 << 8)) + m.String = string(m.Array[:]) + m.Strings = randomNameList(rand) + m.Bytes = m.Array[:] + m.Int = randomInt(rand) + m.Rest = m.Array[:] + return reflect.ValueOf(m) } func TestMarshalUnmarshal(t *testing.T) { rand := rand.New(rand.NewSource(0)) - for i, iface := range messageTypes { - ty := reflect.ValueOf(iface).Type() + iface := &msgAllTypes{} + ty := reflect.ValueOf(iface).Type() - n := 100 - if testing.Short() { - n = 5 + n := 100 + if testing.Short() { + n = 5 + } + for j := 0; j < n; j++ { + v, ok := quick.Value(ty, rand) + if !ok { + t.Errorf("failed to create value") + break } - for j := 0; j < n; j++ { - v, ok := quick.Value(ty, rand) - if !ok { - t.Errorf("#%d: failed to create value", i) - break - } - m1 := v.Elem().Interface() - m2 := iface + m1 := v.Elem().Interface() + m2 := iface - marshaled := marshal(msgIgnore, m1) - if err := unmarshal(m2, marshaled, msgIgnore); err != nil { - t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err) - break - } + marshaled := Marshal(m1) + if err := Unmarshal(marshaled, m2); err != nil { + t.Errorf("Unmarshal %#v: %s", m1, err) + break + } - if !reflect.DeepEqual(v.Interface(), m2) { - t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled) - break - } + if !reflect.DeepEqual(v.Interface(), m2) { + t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled) + break } } } @@ -81,33 +96,37 @@ func TestMarshalUnmarshal(t *testing.T) { func TestUnmarshalEmptyPacket(t *testing.T) { var b []byte var m channelRequestSuccessMsg - err := unmarshal(&m, b, msgChannelRequest) - want := ParseError{msgChannelRequest} - if _, ok := err.(ParseError); !ok { - t.Fatalf("got %T, want %T", err, want) - } - if got := err.(ParseError); want != got { - t.Fatal("got %#v, want %#v", got, want) + if err := Unmarshal(b, &m); err == nil { + t.Fatalf("unmarshal of empty slice succeeded") } } func TestUnmarshalUnexpectedPacket(t *testing.T) { type S struct { - I uint32 + I uint32 `sshtype:"43"` S string B bool } - s := S{42, "hello", true} - packet := marshal(42, s) + s := S{11, "hello", true} + packet := Marshal(s) + packet[0] = 42 roundtrip := S{} - err := unmarshal(&roundtrip, packet, 43) + err := Unmarshal(packet, &roundtrip) if err == nil { t.Fatal("expected error, not nil") } - want := UnexpectedMessageError{43, 42} - if got, ok := err.(UnexpectedMessageError); !ok || want != got { - t.Fatal("expected %q, got %q", want, got) +} + +func TestMarshalPtr(t *testing.T) { + s := struct { + S string + }{"hello"} + + m1 := Marshal(s) + m2 := Marshal(&s) + if !bytes.Equal(m1, m2) { + t.Errorf("got %q, want %q for marshaled pointer", m2, m1) } } @@ -119,9 +138,9 @@ func TestBareMarshalUnmarshal(t *testing.T) { } s := S{42, "hello", true} - packet := marshal(0, s) + packet := Marshal(s) roundtrip := S{} - unmarshal(&roundtrip, packet, 0) + Unmarshal(packet, &roundtrip) if !reflect.DeepEqual(s, roundtrip) { t.Errorf("got %#v, want %#v", roundtrip, s) @@ -133,7 +152,7 @@ func TestBareMarshal(t *testing.T) { I uint32 } s := S2{42} - packet := marshal(0, s) + packet := Marshal(s) i, rest, ok := parseUint32(packet) if len(rest) > 0 || !ok { t.Errorf("parseInt(%q): parse error", packet) @@ -190,43 +209,36 @@ func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { return reflect.ValueOf(dhi) } -// TODO(dfc) maybe this can be removed in the future if testing/quick can handle -// derived basic types. -func (RejectionReason) Generate(rand *rand.Rand, size int) reflect.Value { - m := RejectionReason(Prohibited) - return reflect.ValueOf(m) -} - var ( _kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() _kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() - _kexInit = marshal(msgKexInit, _kexInitMsg) - _kexDHInit = marshal(msgKexDHInit, _kexDHInitMsg) + _kexInit = Marshal(_kexInitMsg) + _kexDHInit = Marshal(_kexDHInitMsg) ) func BenchmarkMarshalKexInitMsg(b *testing.B) { for i := 0; i < b.N; i++ { - marshal(msgKexInit, _kexInitMsg) + Marshal(_kexInitMsg) } } func BenchmarkUnmarshalKexInitMsg(b *testing.B) { m := new(kexInitMsg) for i := 0; i < b.N; i++ { - unmarshal(m, _kexInit, msgKexInit) + Unmarshal(_kexInit, m) } } func BenchmarkMarshalKexDHInitMsg(b *testing.B) { for i := 0; i < b.N; i++ { - marshal(msgKexDHInit, _kexDHInitMsg) + Marshal(_kexDHInitMsg) } } func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) { m := new(kexDHInitMsg) for i := 0; i < b.N; i++ { - unmarshal(m, _kexDHInit, msgKexDHInit) + Unmarshal(_kexDHInit, m) } } diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux.go new file mode 100644 index 00000000..321880ad --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux.go @@ -0,0 +1,356 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "sync" + "sync/atomic" +) + +// debugMux, if set, causes messages in the connection protocol to be +// logged. +const debugMux = false + +// chanList is a thread safe channel list. +type chanList struct { + // protects concurrent access to chans + sync.Mutex + + // chans are indexed by the local id of the channel, which the + // other side should send in the PeersId field. + chans []*channel + + // This is a debugging aid: it offsets all IDs by this + // amount. This helps distinguish otherwise identical + // server/client muxes + offset uint32 +} + +// Assigns a channel ID to the given channel. +func (c *chanList) add(ch *channel) uint32 { + c.Lock() + defer c.Unlock() + for i := range c.chans { + if c.chans[i] == nil { + c.chans[i] = ch + return uint32(i) + c.offset + } + } + c.chans = append(c.chans, ch) + return uint32(len(c.chans)-1) + c.offset +} + +// getChan returns the channel for the given ID. +func (c *chanList) getChan(id uint32) *channel { + id -= c.offset + + c.Lock() + defer c.Unlock() + if id < uint32(len(c.chans)) { + return c.chans[id] + } + return nil +} + +func (c *chanList) remove(id uint32) { + id -= c.offset + c.Lock() + if id < uint32(len(c.chans)) { + c.chans[id] = nil + } + c.Unlock() +} + +// dropAll forgets all channels it knows, returning them in a slice. +func (c *chanList) dropAll() []*channel { + c.Lock() + defer c.Unlock() + var r []*channel + + for _, ch := range c.chans { + if ch == nil { + continue + } + r = append(r, ch) + } + c.chans = nil + return r +} + +// mux represents the state for the SSH connection protocol, which +// multiplexes many channels onto a single packet transport. +type mux struct { + conn packetConn + chanList chanList + + incomingChannels chan NewChannel + + globalSentMu sync.Mutex + globalResponses chan interface{} + incomingRequests chan *Request + + errCond *sync.Cond + err error +} + +// When debugging, each new chanList instantiation has a different +// offset. +var globalOff uint32 + +func (m *mux) Wait() error { + m.errCond.L.Lock() + defer m.errCond.L.Unlock() + for m.err == nil { + m.errCond.Wait() + } + return m.err +} + +// newMux returns a mux that runs over the given connection. +func newMux(p packetConn) *mux { + m := &mux{ + conn: p, + incomingChannels: make(chan NewChannel, 16), + globalResponses: make(chan interface{}, 1), + incomingRequests: make(chan *Request, 16), + errCond: newCond(), + } + if debugMux { + m.chanList.offset = atomic.AddUint32(&globalOff, 1) + } + + go m.loop() + return m +} + +func (m *mux) sendMessage(msg interface{}) error { + p := Marshal(msg) + return m.conn.writePacket(p) +} + +func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) { + if wantReply { + m.globalSentMu.Lock() + defer m.globalSentMu.Unlock() + } + + if err := m.sendMessage(globalRequestMsg{ + Type: name, + WantReply: wantReply, + Data: payload, + }); err != nil { + return false, nil, err + } + + if !wantReply { + return false, nil, nil + } + + msg, ok := <-m.globalResponses + if !ok { + return false, nil, io.EOF + } + switch msg := msg.(type) { + case *globalRequestFailureMsg: + return false, msg.Data, nil + case *globalRequestSuccessMsg: + return true, msg.Data, nil + default: + return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg) + } +} + +// ackRequest must be called after processing a global request that +// has WantReply set. +func (m *mux) ackRequest(ok bool, data []byte) error { + if ok { + return m.sendMessage(globalRequestSuccessMsg{Data: data}) + } + return m.sendMessage(globalRequestFailureMsg{Data: data}) +} + +// TODO(hanwen): Disconnect is a transport layer message. We should +// probably send and receive Disconnect somewhere in the transport +// code. + +// Disconnect sends a disconnect message. +func (m *mux) Disconnect(reason uint32, message string) error { + return m.sendMessage(disconnectMsg{ + Reason: reason, + Message: message, + }) +} + +func (m *mux) Close() error { + return m.conn.Close() +} + +// loop runs the connection machine. It will process packets until an +// error is encountered. To synchronize on loop exit, use mux.Wait. +func (m *mux) loop() { + var err error + for err == nil { + err = m.onePacket() + } + + for _, ch := range m.chanList.dropAll() { + ch.close() + } + + close(m.incomingChannels) + close(m.incomingRequests) + close(m.globalResponses) + + m.conn.Close() + + m.errCond.L.Lock() + m.err = err + m.errCond.Broadcast() + m.errCond.L.Unlock() + + if debugMux { + log.Println("loop exit", err) + } +} + +// onePacket reads and processes one packet. +func (m *mux) onePacket() error { + packet, err := m.conn.readPacket() + if err != nil { + return err + } + + if debugMux { + if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData { + log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet)) + } else { + p, _ := decode(packet) + log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet)) + } + } + + switch packet[0] { + case msgNewKeys: + // Ignore notification of key change. + return nil + case msgDisconnect: + return m.handleDisconnect(packet) + case msgChannelOpen: + return m.handleChannelOpen(packet) + case msgGlobalRequest, msgRequestSuccess, msgRequestFailure: + return m.handleGlobalPacket(packet) + } + + // assume a channel packet. + if len(packet) < 5 { + return parseError(packet[0]) + } + id := binary.BigEndian.Uint32(packet[1:]) + ch := m.chanList.getChan(id) + if ch == nil { + return fmt.Errorf("ssh: invalid channel %d", id) + } + + return ch.handlePacket(packet) +} + +func (m *mux) handleDisconnect(packet []byte) error { + var d disconnectMsg + if err := Unmarshal(packet, &d); err != nil { + return err + } + + if debugMux { + log.Printf("caught disconnect: %v", d) + } + return &d +} + +func (m *mux) handleGlobalPacket(packet []byte) error { + msg, err := decode(packet) + if err != nil { + return err + } + + switch msg := msg.(type) { + case *globalRequestMsg: + m.incomingRequests <- &Request{ + Type: msg.Type, + WantReply: msg.WantReply, + Payload: msg.Data, + mux: m, + } + case *globalRequestSuccessMsg, *globalRequestFailureMsg: + m.globalResponses <- msg + default: + panic(fmt.Sprintf("not a global message %#v", msg)) + } + + return nil +} + +// handleChannelOpen schedules a channel to be Accept()ed. +func (m *mux) handleChannelOpen(packet []byte) error { + var msg channelOpenMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + failMsg := channelOpenFailureMsg{ + PeersId: msg.PeersId, + Reason: ConnectionFailed, + Message: "invalid request", + Language: "en_US.UTF-8", + } + return m.sendMessage(failMsg) + } + + c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData) + c.remoteId = msg.PeersId + c.maxRemotePayload = msg.MaxPacketSize + c.remoteWin.add(msg.PeersWindow) + m.incomingChannels <- c + return nil +} + +func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) { + ch, err := m.openChannel(chanType, extra) + if err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) { + ch := m.newChannel(chanType, channelOutbound, extra) + + ch.maxIncomingPayload = channelMaxPacket + + open := channelOpenMsg{ + ChanType: chanType, + PeersWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + TypeSpecificData: extra, + PeersId: ch.localId, + } + if err := m.sendMessage(open); err != nil { + return nil, err + } + + switch msg := (<-ch.msg).(type) { + case *channelOpenConfirmMsg: + return ch, nil + case *channelOpenFailureMsg: + return nil, &OpenChannelError{msg.Reason, msg.Message} + default: + return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux_test.go new file mode 100644 index 00000000..52303896 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/mux_test.go @@ -0,0 +1,525 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "io/ioutil" + "sync" + "testing" +) + +func muxPair() (*mux, *mux) { + a, b := memPipe() + + s := newMux(a) + c := newMux(b) + + return s, c +} + +// Returns both ends of a channel, and the mux for the the 2nd +// channel. +func channelPair(t *testing.T) (*channel, *channel, *mux) { + c, s := muxPair() + + res := make(chan *channel, 1) + go func() { + newCh, ok := <-s.incomingChannels + if !ok { + t.Fatalf("No incoming channel") + } + if newCh.ChannelType() != "chan" { + t.Fatalf("got type %q want chan", newCh.ChannelType()) + } + ch, _, err := newCh.Accept() + if err != nil { + t.Fatalf("Accept %v", err) + } + res <- ch.(*channel) + }() + + ch, err := c.openChannel("chan", nil) + if err != nil { + t.Fatalf("OpenChannel: %v", err) + } + + return <-res, ch, c +} + +// Test that stderr and stdout can be addressed from different +// goroutines. This is intended for use with the race detector. +func TestMuxChannelExtendedThreadSafety(t *testing.T) { + writer, reader, mux := channelPair(t) + defer writer.Close() + defer reader.Close() + defer mux.Close() + + var wr, rd sync.WaitGroup + magic := "hello world" + + wr.Add(2) + go func() { + io.WriteString(writer, magic) + wr.Done() + }() + go func() { + io.WriteString(writer.Stderr(), magic) + wr.Done() + }() + + rd.Add(2) + go func() { + c, err := ioutil.ReadAll(reader) + if string(c) != magic { + t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err) + } + rd.Done() + }() + go func() { + c, err := ioutil.ReadAll(reader.Stderr()) + if string(c) != magic { + t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err) + } + rd.Done() + }() + + wr.Wait() + writer.CloseWrite() + rd.Wait() +} + +func TestMuxReadWrite(t *testing.T) { + s, c, mux := channelPair(t) + defer s.Close() + defer c.Close() + defer mux.Close() + + magic := "hello world" + magicExt := "hello stderr" + go func() { + _, err := s.Write([]byte(magic)) + if err != nil { + t.Fatalf("Write: %v", err) + } + _, err = s.Extended(1).Write([]byte(magicExt)) + if err != nil { + t.Fatalf("Write: %v", err) + } + err = s.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + }() + + var buf [1024]byte + n, err := c.Read(buf[:]) + if err != nil { + t.Fatalf("server Read: %v", err) + } + got := string(buf[:n]) + if got != magic { + t.Fatalf("server: got %q want %q", got, magic) + } + + n, err = c.Extended(1).Read(buf[:]) + if err != nil { + t.Fatalf("server Read: %v", err) + } + + got = string(buf[:n]) + if got != magicExt { + t.Fatalf("server: got %q want %q", got, magic) + } +} + +func TestMuxChannelOverflow(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + writer.Write(make([]byte, 1)) + wDone <- 1 + }() + writer.remoteWin.waitWriterBlocked() + + // Send 1 byte. + packet := make([]byte, 1+4+4+1) + packet[0] = msgChannelData + marshalUint32(packet[1:], writer.remoteId) + marshalUint32(packet[5:], uint32(1)) + packet[9] = 42 + + if err := writer.mux.conn.writePacket(packet); err != nil { + t.Errorf("could not send packet") + } + if _, err := reader.SendRequest("hello", true, nil); err == nil { + t.Errorf("SendRequest succeeded.") + } + <-wDone +} + +func TestMuxChannelCloseWriteUnblock(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + if _, err := writer.Write(make([]byte, 1)); err != io.EOF { + t.Errorf("got %v, want EOF for unblock write", err) + } + wDone <- 1 + }() + + writer.remoteWin.waitWriterBlocked() + reader.Close() + <-wDone +} + +func TestMuxConnectionCloseWriteUnblock(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + if _, err := writer.Write(make([]byte, 1)); err != io.EOF { + t.Errorf("got %v, want EOF for unblock write", err) + } + wDone <- 1 + }() + + writer.remoteWin.waitWriterBlocked() + mux.Close() + <-wDone +} + +func TestMuxReject(t *testing.T) { + client, server := muxPair() + defer server.Close() + defer client.Close() + + go func() { + ch, ok := <-server.incomingChannels + if !ok { + t.Fatalf("Accept") + } + if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" { + t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData()) + } + ch.Reject(RejectionReason(42), "message") + }() + + ch, err := client.openChannel("ch", []byte("extra")) + if ch != nil { + t.Fatal("openChannel not rejected") + } + + ocf, ok := err.(*OpenChannelError) + if !ok { + t.Errorf("got %#v want *OpenChannelError", err) + } else if ocf.Reason != 42 || ocf.Message != "message" { + t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message") + } + + want := "ssh: rejected: unknown reason 42 (message)" + if err.Error() != want { + t.Errorf("got %q, want %q", err.Error(), want) + } +} + +func TestMuxChannelRequest(t *testing.T) { + client, server, mux := channelPair(t) + defer server.Close() + defer client.Close() + defer mux.Close() + + var received int + var wg sync.WaitGroup + wg.Add(1) + go func() { + for r := range server.incomingRequests { + received++ + r.Reply(r.Type == "yes", nil) + } + wg.Done() + }() + _, err := client.SendRequest("yes", false, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + ok, err := client.SendRequest("yes", true, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + + if !ok { + t.Errorf("SendRequest(yes): %v", ok) + + } + + ok, err = client.SendRequest("no", true, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + if ok { + t.Errorf("SendRequest(no): %v", ok) + + } + + client.Close() + wg.Wait() + + if received != 3 { + t.Errorf("got %d requests, want %d", received, 3) + } +} + +func TestMuxGlobalRequest(t *testing.T) { + clientMux, serverMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + var seen bool + go func() { + for r := range serverMux.incomingRequests { + seen = seen || r.Type == "peek" + if r.WantReply { + err := r.Reply(r.Type == "yes", + append([]byte(r.Type), r.Payload...)) + if err != nil { + t.Errorf("AckRequest: %v", err) + } + } + } + }() + + _, _, err := clientMux.SendRequest("peek", false, nil) + if err != nil { + t.Errorf("SendRequest: %v", err) + } + + ok, data, err := clientMux.SendRequest("yes", true, []byte("a")) + if !ok || string(data) != "yesa" || err != nil { + t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v", + ok, data, err) + } + if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil { + t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v", + ok, data, err) + } + + if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil { + t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v", + ok, data, err) + } + + clientMux.Disconnect(0, "") + if !seen { + t.Errorf("never saw 'peek' request") + } +} + +func TestMuxGlobalRequestUnblock(t *testing.T) { + clientMux, serverMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + result := make(chan error, 1) + go func() { + _, _, err := clientMux.SendRequest("hello", true, nil) + result <- err + }() + + <-serverMux.incomingRequests + serverMux.conn.Close() + err := <-result + + if err != io.EOF { + t.Errorf("want EOF, got %v", io.EOF) + } +} + +func TestMuxChannelRequestUnblock(t *testing.T) { + a, b, connB := channelPair(t) + defer a.Close() + defer b.Close() + defer connB.Close() + + result := make(chan error, 1) + go func() { + _, err := a.SendRequest("hello", true, nil) + result <- err + }() + + <-b.incomingRequests + connB.conn.Close() + err := <-result + + if err != io.EOF { + t.Errorf("want EOF, got %v", err) + } +} + +func TestMuxDisconnect(t *testing.T) { + a, b := muxPair() + defer a.Close() + defer b.Close() + + go func() { + for r := range b.incomingRequests { + r.Reply(true, nil) + } + }() + + a.Disconnect(42, "whatever") + ok, _, err := a.SendRequest("hello", true, nil) + if ok || err == nil { + t.Errorf("got reply after disconnecting") + } + err = b.Wait() + if d, ok := err.(*disconnectMsg); !ok || d.Reason != 42 { + t.Errorf("got %#v, want disconnectMsg{Reason:42}", err) + } +} + +func TestMuxCloseChannel(t *testing.T) { + r, w, mux := channelPair(t) + defer mux.Close() + defer r.Close() + defer w.Close() + + result := make(chan error, 1) + go func() { + var b [1024]byte + _, err := r.Read(b[:]) + result <- err + }() + if err := w.Close(); err != nil { + t.Errorf("w.Close: %v", err) + } + + if _, err := w.Write([]byte("hello")); err != io.EOF { + t.Errorf("got err %v, want io.EOF after Close", err) + } + + if err := <-result; err != io.EOF { + t.Errorf("got %v (%T), want io.EOF", err, err) + } +} + +func TestMuxCloseWriteChannel(t *testing.T) { + r, w, mux := channelPair(t) + defer mux.Close() + + result := make(chan error, 1) + go func() { + var b [1024]byte + _, err := r.Read(b[:]) + result <- err + }() + if err := w.CloseWrite(); err != nil { + t.Errorf("w.CloseWrite: %v", err) + } + + if _, err := w.Write([]byte("hello")); err != io.EOF { + t.Errorf("got err %v, want io.EOF after CloseWrite", err) + } + + if err := <-result; err != io.EOF { + t.Errorf("got %v (%T), want io.EOF", err, err) + } +} + +func TestMuxInvalidRecord(t *testing.T) { + a, b := muxPair() + defer a.Close() + defer b.Close() + + packet := make([]byte, 1+4+4+1) + packet[0] = msgChannelData + marshalUint32(packet[1:], 29348723 /* invalid channel id */) + marshalUint32(packet[5:], 1) + packet[9] = 42 + + a.conn.writePacket(packet) + go a.SendRequest("hello", false, nil) + // 'a' wrote an invalid packet, so 'b' has exited. + req, ok := <-b.incomingRequests + if ok { + t.Errorf("got request %#v after receiving invalid packet", req) + } +} + +func TestZeroWindowAdjust(t *testing.T) { + a, b, mux := channelPair(t) + defer a.Close() + defer b.Close() + defer mux.Close() + + go func() { + io.WriteString(a, "hello") + // bogus adjust. + a.sendMessage(windowAdjustMsg{}) + io.WriteString(a, "world") + a.Close() + }() + + want := "helloworld" + c, _ := ioutil.ReadAll(b) + if string(c) != want { + t.Errorf("got %q want %q", c, want) + } +} + +func TestMuxMaxPacketSize(t *testing.T) { + a, b, mux := channelPair(t) + defer a.Close() + defer b.Close() + defer mux.Close() + + large := make([]byte, a.maxRemotePayload+1) + packet := make([]byte, 1+4+4+1+len(large)) + packet[0] = msgChannelData + marshalUint32(packet[1:], a.remoteId) + marshalUint32(packet[5:], uint32(len(large))) + packet[9] = 42 + + if err := a.mux.conn.writePacket(packet); err != nil { + t.Errorf("could not send packet") + } + + go a.SendRequest("hello", false, nil) + + _, ok := <-b.incomingRequests + if ok { + t.Errorf("connection still alive after receiving large packet.") + } +} + +// Don't ship code with debug=true. +func TestDebug(t *testing.T) { + if debugMux { + t.Error("mux debug switched on") + } + if debugHandshake { + t.Error("handshake debug switched on") + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/server.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/server.go new file mode 100644 index 00000000..8c4f1429 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/server.go @@ -0,0 +1,477 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" +) + +// The Permissions type holds fine-grained permissions that are +// specific to a user or a specific authentication method for a +// user. Permissions, except for "source-address", must be enforced in +// the server application layer, after successful authentication. The +// Permissions are passed on in ServerConn so a server implementation +// can honor them. +type Permissions struct { + // Critical options restrict default permissions. Common + // restrictions are "source-address" and "force-command". If + // the server cannot enforce the restriction, or does not + // recognize it, the user should not authenticate. + CriticalOptions map[string]string + + // Extensions are extra functionality that the server may + // offer on authenticated connections. Common extensions are + // "permit-agent-forwarding", "permit-X11-forwarding". Lack of + // support for an extension does not preclude authenticating a + // user. + Extensions map[string]string +} + +// ServerConfig holds server specific configuration data. +type ServerConfig struct { + // Config contains configuration shared between client and server. + Config + + hostKeys []Signer + + // NoClientAuth is true if clients are allowed to connect without + // authenticating. + NoClientAuth bool + + // PasswordCallback, if non-nil, is called when a user + // attempts to authenticate using a password. + PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) + + // PublicKeyCallback, if non-nil, is called when a client attempts public + // key authentication. It must return true if the given public key is + // valid for the given user. For example, see CertChecker.Authenticate. + PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // KeyboardInteractiveCallback, if non-nil, is called when + // keyboard-interactive authentication is selected (RFC + // 4256). The client object's Challenge function should be + // used to query the user. The callback may offer multiple + // Challenge rounds. To avoid information leaks, the client + // should be presented a challenge even if the user is + // unknown. + KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) + + // AuthLogCallback, if non-nil, is called to log all authentication + // attempts. + AuthLogCallback func(conn ConnMetadata, method string, err error) +} + +// AddHostKey adds a private key as a host key. If an existing host +// key exists with the same algorithm, it is overwritten. Each server +// config must have at least one host key. +func (s *ServerConfig) AddHostKey(key Signer) { + for i, k := range s.hostKeys { + if k.PublicKey().Type() == key.PublicKey().Type() { + s.hostKeys[i] = key + return + } + } + + s.hostKeys = append(s.hostKeys, key) +} + +// cachedPubKey contains the results of querying whether a public key is +// acceptable for a user. +type cachedPubKey struct { + user string + pubKeyData []byte + result error + perms *Permissions +} + +const maxCachedPubKeys = 16 + +// pubKeyCache caches tests for public keys. Since SSH clients +// will query whether a public key is acceptable before attempting to +// authenticate with it, we end up with duplicate queries for public +// key validity. The cache only applies to a single ServerConn. +type pubKeyCache struct { + keys []cachedPubKey +} + +// get returns the result for a given user/algo/key tuple. +func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { + for _, k := range c.keys { + if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { + return k, true + } + } + return cachedPubKey{}, false +} + +// add adds the given tuple to the cache. +func (c *pubKeyCache) add(candidate cachedPubKey) { + if len(c.keys) < maxCachedPubKeys { + c.keys = append(c.keys, candidate) + } +} + +// ServerConn is an authenticated SSH connection, as seen from the +// server +type ServerConn struct { + Conn + + // If the succeeding authentication callback returned a + // non-nil Permissions pointer, it is stored here. + Permissions *Permissions +} + +// NewServerConn starts a new SSH server with c as the underlying +// transport. It starts with a handshake and, if the handshake is +// unsuccessful, it closes the connection and returns an error. The +// Request and NewChannel channels must be serviced, or the connection +// will hang. +func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + s := &connection{ + sshConn: sshConn{conn: c}, + } + perms, err := s.serverHandshake(&fullConf) + if err != nil { + c.Close() + return nil, nil, nil, err + } + return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil +} + +// signAndMarshal signs the data with the appropriate algorithm, +// and serializes the result in SSH wire format. +func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { + sig, err := k.Sign(rand, data) + if err != nil { + return nil, err + } + + return Marshal(sig), nil +} + +// handshake performs key exchange and user authentication. +func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { + if len(config.hostKeys) == 0 { + return nil, errors.New("ssh: server has no host keys") + } + + var err error + s.serverVersion = []byte(packageVersion) + s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) + if err != nil { + return nil, err + } + + tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) + s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) + + if err := s.transport.requestKeyChange(); err != nil { + return nil, err + } + + if packet, err := s.transport.readPacket(); err != nil { + return nil, err + } else if packet[0] != msgNewKeys { + return nil, unexpectedMessageError(msgNewKeys, packet[0]) + } + + var packet []byte + if packet, err = s.transport.readPacket(); err != nil { + return nil, err + } + + var serviceRequest serviceRequestMsg + if err = Unmarshal(packet, &serviceRequest); err != nil { + return nil, err + } + if serviceRequest.Service != serviceUserAuth { + return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") + } + serviceAccept := serviceAcceptMsg{ + Service: serviceUserAuth, + } + if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { + return nil, err + } + + perms, err := s.serverAuthenticate(config) + if err != nil { + return nil, err + } + s.mux = newMux(s.transport) + return perms, err +} + +func isAcceptableAlgo(algo string) bool { + switch algo { + case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: + return true + } + return false +} + +func checkSourceAddress(addr net.Addr, sourceAddr string) error { + if addr == nil { + return errors.New("ssh: no address known for client, but source-address match required") + } + + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) + } + + if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { + if bytes.Equal(allowedIP, tcpAddr.IP) { + return nil + } + } else { + _, ipNet, err := net.ParseCIDR(sourceAddr) + if err != nil { + return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) + } + + if ipNet.Contains(tcpAddr.IP) { + return nil + } + } + + return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) +} + +func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { + var err error + var cache pubKeyCache + var perms *Permissions + +userAuthLoop: + for { + var userAuthReq userAuthRequestMsg + if packet, err := s.transport.readPacket(); err != nil { + return nil, err + } else if err = Unmarshal(packet, &userAuthReq); err != nil { + return nil, err + } + + if userAuthReq.Service != serviceSSH { + return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) + } + + s.user = userAuthReq.User + perms = nil + authErr := errors.New("no auth passed yet") + + switch userAuthReq.Method { + case "none": + if config.NoClientAuth { + s.user = "" + authErr = nil + } + case "password": + if config.PasswordCallback == nil { + authErr = errors.New("ssh: password auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 || payload[0] != 0 { + return nil, parseError(msgUserAuthRequest) + } + payload = payload[1:] + password, payload, ok := parseString(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + perms, authErr = config.PasswordCallback(s, password) + case "keyboard-interactive": + if config.KeyboardInteractiveCallback == nil { + authErr = errors.New("ssh: keyboard-interactive auth not configubred") + break + } + + prompter := &sshClientKeyboardInteractive{s} + perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) + case "publickey": + if config.PublicKeyCallback == nil { + authErr = errors.New("ssh: publickey auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 { + return nil, parseError(msgUserAuthRequest) + } + isQuery := payload[0] == 0 + payload = payload[1:] + algoBytes, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + algo := string(algoBytes) + if !isAcceptableAlgo(algo) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) + break + } + + pubKeyData, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + + pubKey, err := ParsePublicKey(pubKeyData) + if err != nil { + return nil, err + } + + candidate, ok := cache.get(s.user, pubKeyData) + if !ok { + candidate.user = s.user + candidate.pubKeyData = pubKeyData + candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) + if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { + candidate.result = checkSourceAddress( + s.RemoteAddr(), + candidate.perms.CriticalOptions[sourceAddressCriticalOption]) + } + cache.add(candidate) + } + + if isQuery { + // The client can query if the given public key + // would be okay. + if len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + if candidate.result == nil { + okMsg := userAuthPubKeyOkMsg{ + Algo: algo, + PubKey: pubKeyData, + } + if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { + return nil, err + } + continue userAuthLoop + } + authErr = candidate.result + } else { + sig, payload, ok := parseSignature(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + // Ensure the public key algo and signature algo + // are supported. Compare the private key + // algorithm name that corresponds to algo with + // sig.Format. This is usually the same, but + // for certs, the names differ. + if !isAcceptableAlgo(sig.Format) { + break + } + signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData) + + if err := pubKey.Verify(signedData, sig); err != nil { + return nil, err + } + + authErr = candidate.result + perms = candidate.perms + } + default: + authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) + } + + if config.AuthLogCallback != nil { + config.AuthLogCallback(s, userAuthReq.Method, authErr) + } + + if authErr == nil { + break userAuthLoop + } + + var failureMsg userAuthFailureMsg + if config.PasswordCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "password") + } + if config.PublicKeyCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "publickey") + } + if config.KeyboardInteractiveCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") + } + + if len(failureMsg.Methods) == 0 { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil { + return nil, err + } + } + + if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { + return nil, err + } + return perms, nil +} + +// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by +// asking the client on the other side of a ServerConn. +type sshClientKeyboardInteractive struct { + *connection +} + +func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + if len(questions) != len(echos) { + return nil, errors.New("ssh: echos and questions must have equal length") + } + + var prompts []byte + for i := range questions { + prompts = appendString(prompts, questions[i]) + prompts = appendBool(prompts, echos[i]) + } + + if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ + Instruction: instruction, + NumPrompts: uint32(len(questions)), + Prompts: prompts, + })); err != nil { + return nil, err + } + + packet, err := c.transport.readPacket() + if err != nil { + return nil, err + } + if packet[0] != msgUserAuthInfoResponse { + return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) + } + packet = packet[1:] + + n, packet, ok := parseUint32(packet) + if !ok || int(n) != len(questions) { + return nil, parseError(msgUserAuthInfoResponse) + } + + for i := uint32(0); i < n; i++ { + ans, rest, ok := parseString(packet) + if !ok { + return nil, parseError(msgUserAuthInfoResponse) + } + + answers = append(answers, string(ans)) + packet = rest + } + if len(packet) != 0 { + return nil, errors.New("ssh: junk at end of message") + } + + return answers, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session.go similarity index 69% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session.go index 39f2d223..3b42b508 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session.go @@ -129,128 +129,126 @@ type Session struct { Stdout io.Writer Stderr io.Writer - *clientChan // the channel backing this session - - started bool // true once Start, Run or Shell is invoked. + ch Channel // the channel backing this session + started bool // true once Start, Run or Shell is invoked. copyFuncs []func() error errors chan error // one send per copyFunc // true if pipe method is active stdinpipe, stdoutpipe, stderrpipe bool + + // stdinPipeWriter is non-nil if StdinPipe has not been called + // and Stdin was specified by the user; it is the write end of + // a pipe connecting Session.Stdin to the stdin channel. + stdinPipeWriter io.WriteCloser + + exitStatus chan error +} + +// SendRequest sends an out-of-band channel request on the SSH channel +// underlying the session. +func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + return s.ch.SendRequest(name, wantReply, payload) +} + +func (s *Session) Close() error { + return s.ch.Close() } // RFC 4254 Section 6.4. type setenvRequest struct { - PeersId uint32 - Request string - WantReply bool - Name string - Value string -} - -// RFC 4254 Section 6.5. -type subsystemRequestMsg struct { - PeersId uint32 - Request string - WantReply bool - Subsystem string + Name string + Value string } // Setenv sets an environment variable that will be applied to any // command executed by Shell or Run. func (s *Session) Setenv(name, value string) error { - req := setenvRequest{ - PeersId: s.remoteId, - Request: "env", - WantReply: true, - Name: name, - Value: value, + msg := setenvRequest{ + Name: name, + Value: value, } - if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { - return err + ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: setenv failed") } - return s.waitForResponse() + return err } // RFC 4254 Section 6.2. type ptyRequestMsg struct { - PeersId uint32 - Request string - WantReply bool - Term string - Columns uint32 - Rows uint32 - Width uint32 - Height uint32 - Modelist string + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string } // RequestPty requests the association of a pty with the session on the remote host. func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { var tm []byte for k, v := range termmodes { - tm = append(tm, k) - tm = appendU32(tm, v) + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, Marshal(&kv)...) } tm = append(tm, tty_OP_END) req := ptyRequestMsg{ - PeersId: s.remoteId, - Request: "pty-req", - WantReply: true, - Term: term, - Columns: uint32(w), - Rows: uint32(h), - Width: uint32(w * 8), - Height: uint32(h * 8), - Modelist: string(tm), + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: string(tm), } - if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { - return err + ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) + if err == nil && !ok { + err = errors.New("ssh: pty-req failed") } - return s.waitForResponse() + return err +} + +// RFC 4254 Section 6.5. +type subsystemRequestMsg struct { + Subsystem string } // RequestSubsystem requests the association of a subsystem with the session on the remote host. // A subsystem is a predefined command that runs in the background when the ssh session is initiated func (s *Session) RequestSubsystem(subsystem string) error { - req := subsystemRequestMsg{ - PeersId: s.remoteId, - Request: "subsystem", - WantReply: true, + msg := subsystemRequestMsg{ Subsystem: subsystem, } - if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { - return err + ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: subsystem request failed") } - return s.waitForResponse() + return err } // RFC 4254 Section 6.9. type signalMsg struct { - PeersId uint32 - Request string - WantReply bool - Signal string + Signal string } // Signal sends the given signal to the remote process. // sig is one of the SIG* constants. func (s *Session) Signal(sig Signal) error { - req := signalMsg{ - PeersId: s.remoteId, - Request: "signal", - WantReply: false, - Signal: string(sig), + msg := signalMsg{ + Signal: string(sig), } - return s.writePacket(marshal(msgChannelRequest, req)) + + _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) + return err } // RFC 4254 Section 6.5. type execMsg struct { - PeersId uint32 - Request string - WantReply bool - Command string + Command string } // Start runs cmd on the remote host. Typically, the remote @@ -261,17 +259,16 @@ func (s *Session) Start(cmd string) error { return errors.New("ssh: session already started") } req := execMsg{ - PeersId: s.remoteId, - Request: "exec", - WantReply: true, - Command: cmd, + Command: cmd, } - if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + + ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) + if err == nil && !ok { + err = fmt.Errorf("ssh: command %v failed", cmd) + } + if err != nil { return err } - if err := s.waitForResponse(); err != nil { - return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err) - } return s.start() } @@ -339,31 +336,17 @@ func (s *Session) Shell() error { if s.started { return errors.New("ssh: session already started") } - req := channelRequestMsg{ - PeersId: s.remoteId, - Request: "shell", - WantReply: true, + + ok, err := s.ch.SendRequest("shell", true, nil) + if err == nil && !ok { + return fmt.Errorf("ssh: cound not start shell") } - if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil { + if err != nil { return err } - if err := s.waitForResponse(); err != nil { - return fmt.Errorf("ssh: could not execute shell: %v", err) - } return s.start() } -func (s *Session) waitForResponse() error { - msg := <-s.msg - switch msg.(type) { - case *channelRequestSuccessMsg: - return nil - case *channelRequestFailureMsg: - return errors.New("ssh: request failed") - } - return fmt.Errorf("ssh: unknown packet %T received: %v", msg, msg) -} - func (s *Session) start() error { s.started = true @@ -394,8 +377,11 @@ func (s *Session) Wait() error { if !s.started { return errors.New("ssh: session not started") } - waitErr := s.wait() + waitErr := <-s.exitStatus + if s.stdinPipeWriter != nil { + s.stdinPipeWriter.Close() + } var copyError error for _ = range s.copyFuncs { if err := <-s.errors; err != nil && copyError == nil { @@ -408,52 +394,35 @@ func (s *Session) Wait() error { return copyError } -func (s *Session) wait() error { +func (s *Session) wait(reqs <-chan *Request) error { wm := Waitmsg{status: -1} - // Wait for msg channel to be closed before returning. - for msg := range s.msg { - switch msg := msg.(type) { - case *channelRequestMsg: - switch msg.Request { - case "exit-status": - d := msg.RequestSpecificData - wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) - case "exit-signal": - signal, rest, ok := parseString(msg.RequestSpecificData) - if !ok { - return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) - } - wm.signal = safeString(string(signal)) - - // skip coreDumped bool - if len(rest) == 0 { - return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) - } - rest = rest[1:] - - errmsg, rest, ok := parseString(rest) - if !ok { - return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) - } - wm.msg = safeString(string(errmsg)) - - lang, _, ok := parseString(rest) - if !ok { - return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData) - } - wm.lang = safeString(string(lang)) - default: - // This handles keepalives and matches - // OpenSSH's behaviour. - if msg.WantReply { - s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{ - PeersId: s.remoteId, - })) - } + for msg := range reqs { + switch msg.Type { + case "exit-status": + d := msg.Payload + wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3]) + case "exit-signal": + var sigval struct { + Signal string + CoreDumped bool + Error string + Lang string } + if err := Unmarshal(msg.Payload, &sigval); err != nil { + return err + } + + // Must sanitize strings? + wm.signal = sigval.Signal + wm.msg = sigval.Error + wm.lang = sigval.Lang default: - return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) + // This handles keepalives and matches + // OpenSSH's behaviour. + if msg.WantReply { + msg.Reply(false, nil) + } } } if wm.status == 0 { @@ -476,12 +445,20 @@ func (s *Session) stdin() { if s.stdinpipe { return } + var stdin io.Reader if s.Stdin == nil { - s.Stdin = new(bytes.Buffer) + stdin = new(bytes.Buffer) + } else { + r, w := io.Pipe() + go func() { + _, err := io.Copy(w, s.Stdin) + w.CloseWithError(err) + }() + stdin, s.stdinPipeWriter = r, w } s.copyFuncs = append(s.copyFuncs, func() error { - _, err := io.Copy(s.clientChan.stdin, s.Stdin) - if err1 := s.clientChan.stdin.Close(); err == nil && err1 != io.EOF { + _, err := io.Copy(s.ch, stdin) + if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { err = err1 } return err @@ -496,7 +473,7 @@ func (s *Session) stdout() { s.Stdout = ioutil.Discard } s.copyFuncs = append(s.copyFuncs, func() error { - _, err := io.Copy(s.Stdout, s.clientChan.stdout) + _, err := io.Copy(s.Stdout, s.ch) return err }) } @@ -509,11 +486,21 @@ func (s *Session) stderr() { s.Stderr = ioutil.Discard } s.copyFuncs = append(s.copyFuncs, func() error { - _, err := io.Copy(s.Stderr, s.clientChan.stderr) + _, err := io.Copy(s.Stderr, s.ch.Stderr()) return err }) } +// sessionStdin reroutes Close to CloseWrite. +type sessionStdin struct { + io.Writer + ch Channel +} + +func (s *sessionStdin) Close() error { + return s.ch.CloseWrite() +} + // StdinPipe returns a pipe that will be connected to the // remote command's standard input when the command starts. func (s *Session) StdinPipe() (io.WriteCloser, error) { @@ -524,7 +511,7 @@ func (s *Session) StdinPipe() (io.WriteCloser, error) { return nil, errors.New("ssh: StdinPipe after process started") } s.stdinpipe = true - return s.clientChan.stdin, nil + return &sessionStdin{s.ch, s.ch}, nil } // StdoutPipe returns a pipe that will be connected to the @@ -541,7 +528,7 @@ func (s *Session) StdoutPipe() (io.Reader, error) { return nil, errors.New("ssh: StdoutPipe after process started") } s.stdoutpipe = true - return s.clientChan.stdout, nil + return s.ch, nil } // StderrPipe returns a pipe that will be connected to the @@ -558,28 +545,20 @@ func (s *Session) StderrPipe() (io.Reader, error) { return nil, errors.New("ssh: StderrPipe after process started") } s.stderrpipe = true - return s.clientChan.stderr, nil + return s.ch.Stderr(), nil } -// NewSession returns a new interactive session on the remote host. -func (c *ClientConn) NewSession() (*Session, error) { - ch := c.newChan(c.transport) - if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenMsg{ - ChanType: "session", - PeersId: ch.localId, - PeersWindow: channelWindowSize, - MaxPacketSize: channelMaxPacketSize, - })); err != nil { - c.chanList.remove(ch.localId) - return nil, err +// newSession returns a new interactive session on the remote host. +func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { + s := &Session{ + ch: ch, } - if err := ch.waitForChannelOpenResponse(); err != nil { - c.chanList.remove(ch.localId) - return nil, fmt.Errorf("ssh: unable to open session: %v", err) - } - return &Session{ - clientChan: ch, - }, nil + s.exitStatus = make(chan error, 1) + go func() { + s.exitStatus <- s.wait(reqs) + }() + + return s, nil } // An ExitError reports unsuccessful completion of a remote command. diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session_test.go similarity index 60% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session_test.go index 5cff58a9..fce98682 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/session_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/session_test.go @@ -12,71 +12,60 @@ import ( "io" "io/ioutil" "math/rand" - "net" "testing" - "code.google.com/p/go.crypto/ssh/terminal" + "golang.org/x/crypto/ssh/terminal" ) -type serverType func(*serverChan, *testing.T) +type serverType func(Channel, <-chan *Request, *testing.T) // dial constructs a new test server and returns a *ClientConn. -func dial(handler serverType, t *testing.T) *ClientConn { - l, err := Listen("tcp", "127.0.0.1:0", serverConfig) +func dial(handler serverType, t *testing.T) *Client { + c1, c2, err := netPipe() if err != nil { - t.Fatalf("unable to listen: %v", err) + t.Fatalf("netPipe: %v", err) } + go func() { - defer l.Close() - conn, err := l.Accept() + defer c1.Close() + conf := ServerConfig{ + NoClientAuth: true, + } + conf.AddHostKey(testSigners["rsa"]) + + _, chans, reqs, err := NewServerConn(c1, &conf) if err != nil { - t.Errorf("Unable to accept: %v", err) - return + t.Fatalf("Unable to handshake: %v", err) } - defer conn.Close() - if err := conn.Handshake(); err != nil { - t.Errorf("Unable to handshake: %v", err) - return - } - done := make(chan struct{}) - for { - ch, err := conn.Accept() - if err == io.EOF || err == io.ErrUnexpectedEOF { - return - } - // We sometimes get ECONNRESET rather than EOF. - if _, ok := err.(*net.OpError); ok { - return - } - if err != nil { - t.Errorf("Unable to accept incoming channel request: %v", err) - return - } - if ch.ChannelType() != "session" { - ch.Reject(UnknownChannelType, "unknown channel type") + go DiscardRequests(reqs) + + for newCh := range chans { + if newCh.ChannelType() != "session" { + newCh.Reject(UnknownChannelType, "unknown channel type") + continue + } + + ch, inReqs, err := newCh.Accept() + if err != nil { + t.Errorf("Accept: %v", err) continue } - ch.Accept() go func() { - defer close(done) - handler(ch.(*serverChan), t) + handler(ch, inReqs, t) }() } - <-done }() config := &ClientConfig{ User: "testuser", - Auth: []ClientAuth{ - ClientAuthPassword(clientPassword), - }, } - c, err := Dial("tcp", l.Addr().String(), config) + conn, chans, reqs, err := NewClientConn(c2, "", config) if err != nil { t.Fatalf("unable to dial remote side: %v", err) } - return c + + return NewClient(conn, chans, reqs) } // Test a simple string is returned to session.Stdout. @@ -330,164 +319,6 @@ func TestExitWithoutStatusOrSignal(t *testing.T) { } } -func TestInvalidServerMessage(t *testing.T) { - conn := dial(sendInvalidRecord, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - // Make sure that we closed all the clientChans when the connection - // failed. - session.wait() - - defer session.Close() -} - -// In the wild some clients (and servers) send zero sized window updates. -// Test that the client can continue after receiving a zero sized update. -func TestClientZeroWindowAdjust(t *testing.T) { - conn := dial(sendZeroWindowAdjust, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - defer session.Close() - - if err := session.Shell(); err != nil { - t.Fatalf("Unable to execute command: %v", err) - } - err = session.Wait() - if err != nil { - t.Fatalf("expected nil but got %v", err) - } -} - -// In the wild some clients (and servers) send zero sized window updates. -// Test that the server can continue after receiving a zero size update. -func TestServerZeroWindowAdjust(t *testing.T) { - conn := dial(exitStatusZeroHandler, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - defer session.Close() - - if err := session.Shell(); err != nil { - t.Fatalf("Unable to execute command: %v", err) - } - - // send a bogus zero sized window update - session.clientChan.sendWindowAdj(0) - - err = session.Wait() - if err != nil { - t.Fatalf("expected nil but got %v", err) - } -} - -// Verify that the client never sends a packet larger than maxpacket. -func TestClientStdinRespectsMaxPacketSize(t *testing.T) { - conn := dial(discardHandler, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("failed to request new session: %v", err) - } - defer session.Close() - stdin, err := session.StdinPipe() - if err != nil { - t.Fatalf("failed to obtain stdinpipe: %v", err) - } - const size = 100 * 1000 - for i := 0; i < 10; i++ { - n, err := stdin.Write(make([]byte, size)) - if n != size || err != nil { - t.Fatalf("failed to write: %d, %v", n, err) - } - } -} - -// Verify that the client never accepts a packet larger than maxpacket. -func TestServerStdoutRespectsMaxPacketSize(t *testing.T) { - conn := dial(largeSendHandler, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - defer session.Close() - out, err := session.StdoutPipe() - if err != nil { - t.Fatalf("Unable to connect to Stdout: %v", err) - } - if err := session.Shell(); err != nil { - t.Fatalf("Unable to execute command: %v", err) - } - if _, err := ioutil.ReadAll(out); err != nil { - t.Fatalf("failed to read: %v", err) - } -} - -func TestClientCannotSendAfterEOF(t *testing.T) { - conn := dial(exitWithoutSignalOrStatus, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - defer session.Close() - in, err := session.StdinPipe() - if err != nil { - t.Fatalf("Unable to connect channel stdin: %v", err) - } - if err := session.Shell(); err != nil { - t.Fatalf("Unable to execute command: %v", err) - } - if err := in.Close(); err != nil { - t.Fatalf("Unable to close stdin: %v", err) - } - if _, err := in.Write([]byte("foo")); err == nil { - t.Fatalf("Session write should fail") - } -} - -func TestClientCannotSendAfterClose(t *testing.T) { - conn := dial(exitWithoutSignalOrStatus, t) - defer conn.Close() - session, err := conn.NewSession() - if err != nil { - t.Fatalf("Unable to request new session: %v", err) - } - defer session.Close() - in, err := session.StdinPipe() - if err != nil { - t.Fatalf("Unable to connect channel stdin: %v", err) - } - if err := session.Shell(); err != nil { - t.Fatalf("Unable to execute command: %v", err) - } - // close underlying channel - if err := session.channel.Close(); err != nil { - t.Fatalf("Unable to close session: %v", err) - } - if _, err := in.Write([]byte("foo")); err == nil { - t.Fatalf("Session write should fail") - } -} - -func TestClientCannotSendHugePacket(t *testing.T) { - // client and server use the same transport write code so this - // test suffices for both. - conn := dial(shellHandler, t) - defer conn.Close() - if err := conn.transport.writePacket(make([]byte, maxPacket*2)); err == nil { - t.Fatalf("huge packet write should fail") - } -} - // windowTestBytes is the number of bytes that we'll send to the SSH server. const windowTestBytes = 16000 * 200 @@ -560,93 +391,104 @@ func TestClientHandlesKeepalives(t *testing.T) { } type exitStatusMsg struct { - PeersId uint32 - Request string - WantReply bool - Status uint32 + Status uint32 } type exitSignalMsg struct { - PeersId uint32 - Request string - WantReply bool Signal string CoreDumped bool Errmsg string Lang string } -func newServerShell(ch *serverChan, prompt string) *ServerTerminal { - term := terminal.NewTerminal(ch, prompt) - return &ServerTerminal{ - Term: term, - Channel: ch, +func handleTerminalRequests(in <-chan *Request) { + for req := range in { + ok := false + switch req.Type { + case "shell": + ok = true + if len(req.Payload) > 0 { + // We don't accept any commands, only the default shell. + ok = false + } + case "env": + ok = true + } + req.Reply(ok, nil) } } -func exitStatusZeroHandler(ch *serverChan, t *testing.T) { +func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal { + term := terminal.NewTerminal(ch, prompt) + go handleTerminalRequests(in) + return term +} + +func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() // this string is returned to stdout - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) sendStatus(0, ch, t) } -func exitStatusNonZeroHandler(ch *serverChan, t *testing.T) { +func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) sendStatus(15, ch, t) } -func exitSignalAndStatusHandler(ch *serverChan, t *testing.T) { +func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) sendStatus(15, ch, t) sendSignal("TERM", ch, t) } -func exitSignalHandler(ch *serverChan, t *testing.T) { +func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) sendSignal("TERM", ch, t) } -func exitSignalUnknownHandler(ch *serverChan, t *testing.T) { +func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) sendSignal("SYS", ch, t) } -func exitWithoutSignalOrStatus(ch *serverChan, t *testing.T) { +func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) } -func shellHandler(ch *serverChan, t *testing.T) { +func shellHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() // this string is returned to stdout - shell := newServerShell(ch, "golang") + shell := newServerShell(ch, in, "golang") readLine(shell, t) sendStatus(0, ch, t) } // Ignores the command, writes fixed strings to stderr and stdout. // Strings are "this-is-stdout." and "this-is-stderr.". -func fixedOutputHandler(ch *serverChan, t *testing.T) { +func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() + _, err := ch.Read(nil) - _, err := ch.Read(make([]byte, 0)) - if _, ok := err.(ChannelRequest); !ok { + req, ok := <-in + if !ok { t.Fatalf("error: expected channel request, got: %#v", err) return } + // ignore request, always send some text - ch.AckRequest(true) + req.Reply(true, nil) _, err = io.WriteString(ch, "this-is-stdout.") if err != nil { @@ -659,84 +501,39 @@ func fixedOutputHandler(ch *serverChan, t *testing.T) { sendStatus(0, ch, t) } -func readLine(shell *ServerTerminal, t *testing.T) { +func readLine(shell *terminal.Terminal, t *testing.T) { if _, err := shell.ReadLine(); err != nil && err != io.EOF { t.Errorf("unable to read line: %v", err) } } -func sendStatus(status uint32, ch *serverChan, t *testing.T) { +func sendStatus(status uint32, ch Channel, t *testing.T) { msg := exitStatusMsg{ - PeersId: ch.remoteId, - Request: "exit-status", - WantReply: false, - Status: status, + Status: status, } - if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil { + if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil { t.Errorf("unable to send status: %v", err) } } -func sendSignal(signal string, ch *serverChan, t *testing.T) { +func sendSignal(signal string, ch Channel, t *testing.T) { sig := exitSignalMsg{ - PeersId: ch.remoteId, - Request: "exit-signal", - WantReply: false, Signal: signal, CoreDumped: false, Errmsg: "Process terminated", Lang: "en-GB-oed", } - if err := ch.writePacket(marshal(msgChannelRequest, sig)); err != nil { + if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil { t.Errorf("unable to send signal: %v", err) } } -func sendInvalidRecord(ch *serverChan, t *testing.T) { +func discardHandler(ch Channel, t *testing.T) { defer ch.Close() - packet := make([]byte, 1+4+4+1) - packet[0] = msgChannelData - marshalUint32(packet[1:], 29348723 /* invalid channel id */) - marshalUint32(packet[5:], 1) - packet[9] = 42 - - if err := ch.writePacket(packet); err != nil { - t.Errorf("unable send invalid record: %v", err) - } -} - -func sendZeroWindowAdjust(ch *serverChan, t *testing.T) { - defer ch.Close() - // send a bogus zero sized window update - ch.sendWindowAdj(0) - shell := newServerShell(ch, "> ") - readLine(shell, t) - sendStatus(0, ch, t) -} - -func discardHandler(ch *serverChan, t *testing.T) { - defer ch.Close() - // grow the window to avoid being fooled by - // the initial 1 << 14 window. - ch.sendWindowAdj(1024 * 1024) io.Copy(ioutil.Discard, ch) } -func largeSendHandler(ch *serverChan, t *testing.T) { - defer ch.Close() - // grow the window to avoid being fooled by - // the initial 1 << 14 window. - ch.sendWindowAdj(1024 * 1024) - shell := newServerShell(ch, "> ") - readLine(shell, t) - // try to send more than the 32k window - // will allow - if err := ch.writePacket(make([]byte, 128*1024)); err == nil { - t.Errorf("wrote packet larger than 32k") - } -} - -func echoHandler(ch *serverChan, t *testing.T) { +func echoHandler(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil { t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err) @@ -773,17 +570,59 @@ func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, erro return written, nil } -func channelKeepaliveSender(ch *serverChan, t *testing.T) { +func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) { defer ch.Close() - shell := newServerShell(ch, "> ") + shell := newServerShell(ch, in, "> ") readLine(shell, t) - msg := channelRequestMsg{ - PeersId: ch.remoteId, - Request: "keepalive@openssh.com", - WantReply: true, - } - if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil { + if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil { t.Errorf("unable to send channel keepalive request: %v", err) } sendStatus(0, ch, t) } + +func TestClientWriteEOF(t *testing.T) { + conn := dial(simpleEchoHandler, t) + defer conn.Close() + + session, err := conn.NewSession() + if err != nil { + t.Fatal(err) + } + defer session.Close() + stdin, err := session.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe failed: %v", err) + } + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("StdoutPipe failed: %v", err) + } + + data := []byte(`0000`) + _, err = stdin.Write(data) + if err != nil { + t.Fatalf("Write failed: %v", err) + } + stdin.Close() + + res, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatalf("Read failed: %v", err) + } + + if !bytes.Equal(data, res) { + t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res) + } +} + +func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + data, err := ioutil.ReadAll(ch) + if err != nil { + t.Errorf("handler read error: %v", err) + } + _, err = ch.Write(data) + if err != nil { + t.Errorf("handler write error: %v", err) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip.go similarity index 65% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip.go index 74fc1a7c..4ecad0b3 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip.go @@ -16,10 +16,11 @@ import ( "time" ) -// Listen requests the remote peer open a listening socket -// on addr. Incoming connections will be available by calling -// Accept on the returned net.Listener. -func (c *ClientConn) Listen(n, addr string) (net.Listener, error) { +// Listen requests the remote peer open a listening socket on +// addr. Incoming connections will be available by calling Accept on +// the returned net.Listener. The listener must be serviced, or the +// SSH connection may hang. +func (c *Client) Listen(n, addr string) (net.Listener, error) { laddr, err := net.ResolveTCPAddr(n, addr) if err != nil { return nil, err @@ -59,7 +60,7 @@ func isBrokenOpenSSHVersion(versionStr string) bool { // autoPortListenWorkaround simulates automatic port allocation by // trying random ports repeatedly. -func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { +func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { var sshListener net.Listener var err error const tries = 10 @@ -77,44 +78,45 @@ func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, // RFC 4254 7.1 type channelForwardMsg struct { - Message string - WantReply bool - raddr string - rport uint32 + addr string + rport uint32 } // ListenTCP requests the remote peer open a listening socket // on laddr. Incoming connections will be available by calling // Accept on the returned net.Listener. -func (c *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { - if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) { +func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { + if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { return c.autoPortListenWorkaround(laddr) } m := channelForwardMsg{ - "tcpip-forward", - true, // sendGlobalRequest waits for a reply laddr.IP.String(), uint32(laddr.Port), } // send message - resp, err := c.sendGlobalRequest(m) + ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m)) if err != nil { return nil, err } + if !ok { + return nil, errors.New("ssh: tcpip-forward request denied by peer") + } // If the original port was 0, then the remote side will // supply a real port number in the response. if laddr.Port == 0 { - port, _, ok := parseUint32(resp.Data) - if !ok { - return nil, errors.New("unable to parse response") + var p struct { + Port uint32 } - laddr.Port = int(port) + if err := Unmarshal(resp, &p); err != nil { + return nil, err + } + laddr.Port = int(p.Port) } // Register this forward, using the port number we obtained. - ch := c.forwardList.add(*laddr) + ch := c.forwards.add(*laddr) return &tcpListener{laddr, c, ch}, nil } @@ -137,7 +139,7 @@ type forwardEntry struct { // arguments to add/remove/lookup should be address as specified in // the original forward-request. type forward struct { - c *clientChan // the ssh client channel underlying this forward + newCh NewChannel // the ssh client channel underlying this forward raddr *net.TCPAddr // the raddr of the incoming connection } @@ -152,6 +154,59 @@ func (l *forwardList) add(addr net.TCPAddr) chan forward { return f.c } +// See RFC 4254, section 7.2 +type forwardedTCPPayload struct { + Addr string + Port uint32 + OriginAddr string + OriginPort uint32 +} + +// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr. +func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { + if port == 0 || port > 65535 { + return nil, fmt.Errorf("ssh: port number out of range: %d", port) + } + ip := net.ParseIP(string(addr)) + if ip == nil { + return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr) + } + return &net.TCPAddr{IP: ip, Port: int(port)}, nil +} + +func (l *forwardList) handleChannels(in <-chan NewChannel) { + for ch := range in { + var payload forwardedTCPPayload + if err := Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) + continue + } + + // RFC 4254 section 7.2 specifies that incoming + // addresses should list the address, in string + // format. It is implied that this should be an IP + // address, as it would be impossible to connect to it + // otherwise. + laddr, err := parseTCPAddr(payload.Addr, payload.Port) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + + if ok := l.forward(*laddr, *raddr, ch); !ok { + // Section 7.2, implementations MUST reject spurious incoming + // connections. + ch.Reject(Prohibited, "no forward for address") + continue + } + } +} + // remove removes the forward entry, and the channel feeding its // listener. func (l *forwardList) remove(addr net.TCPAddr) { @@ -176,21 +231,22 @@ func (l *forwardList) closeAll() { l.entries = nil } -func (l *forwardList) lookup(addr net.TCPAddr) (chan forward, bool) { +func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool { l.Lock() defer l.Unlock() for _, f := range l.entries { - if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { - return f.c, true + if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port { + f.c <- forward{ch, &raddr} + return true } } - return nil, false + return false } type tcpListener struct { laddr *net.TCPAddr - conn *ClientConn + conn *Client in <-chan forward } @@ -200,30 +256,33 @@ func (l *tcpListener) Accept() (net.Conn, error) { if !ok { return nil, io.EOF } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + return &tcpChanConn{ - tcpChan: &tcpChan{ - clientChan: s.c, - Reader: s.c.stdout, - Writer: s.c.stdin, - }, - laddr: l.laddr, - raddr: s.raddr, + Channel: ch, + laddr: l.laddr, + raddr: s.raddr, }, nil } // Close closes the listener. func (l *tcpListener) Close() error { m := channelForwardMsg{ - "cancel-tcpip-forward", - true, l.laddr.IP.String(), uint32(l.laddr.Port), } - l.conn.forwardList.remove(*l.laddr) - if _, err := l.conn.sendGlobalRequest(m); err != nil { - return err + + // this also closes the listener. + l.conn.forwards.remove(*l.laddr) + ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-tcpip-forward failed") } - return nil + return err } // Addr returns the listener's network address. @@ -233,7 +292,7 @@ func (l *tcpListener) Addr() net.Addr { // Dial initiates a connection to the addr from the remote host. // The resulting connection has a zero LocalAddr() and RemoteAddr(). -func (c *ClientConn) Dial(n, addr string) (net.Conn, error) { +func (c *Client) Dial(n, addr string) (net.Conn, error) { // Parse the address into host and numeric port. host, portString, err := net.SplitHostPort(addr) if err != nil { @@ -253,7 +312,7 @@ func (c *ClientConn) Dial(n, addr string) (net.Conn, error) { return nil, err } return &tcpChanConn{ - tcpChan: ch, + Channel: ch, laddr: zeroAddr, raddr: zeroAddr, }, nil @@ -262,7 +321,7 @@ func (c *ClientConn) Dial(n, addr string) (net.Conn, error) { // DialTCP connects to the remote address raddr on the network net, // which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used // as the local address for the connection. -func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { +func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { if laddr == nil { laddr = &net.TCPAddr{ IP: net.IPv4zero, @@ -274,7 +333,7 @@ func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, err return nil, err } return &tcpChanConn{ - tcpChan: ch, + Channel: ch, laddr: laddr, raddr: raddr, }, nil @@ -282,54 +341,32 @@ func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, err // RFC 4254 7.2 type channelOpenDirectMsg struct { - ChanType string - PeersId uint32 - PeersWindow uint32 - MaxPacketSize uint32 - raddr string - rport uint32 - laddr string - lport uint32 + raddr string + rport uint32 + laddr string + lport uint32 } -// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as -// strings and are expected to be resolvable at the remote end. -func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpChan, error) { - ch := c.newChan(c.transport) - if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{ - ChanType: "direct-tcpip", - PeersId: ch.localId, - PeersWindow: channelWindowSize, - MaxPacketSize: channelMaxPacketSize, - raddr: raddr, - rport: uint32(rport), - laddr: laddr, - lport: uint32(lport), - })); err != nil { - c.chanList.remove(ch.localId) - return nil, err +func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) { + msg := channelOpenDirectMsg{ + raddr: raddr, + rport: uint32(rport), + laddr: laddr, + lport: uint32(lport), } - if err := ch.waitForChannelOpenResponse(); err != nil { - c.chanList.remove(ch.localId) - return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err) - } - return &tcpChan{ - clientChan: ch, - Reader: ch.stdout, - Writer: ch.stdin, - }, nil + ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg)) + go DiscardRequests(in) + return ch, err } type tcpChan struct { - *clientChan // the backing channel - io.Reader - io.Writer + Channel // the backing channel } // tcpChanConn fulfills the net.Conn interface without // the tcpChan having to hold laddr or raddr directly. type tcpChanConn struct { - *tcpChan + Channel laddr, raddr net.Addr } diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip_test.go similarity index 67% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip_test.go index 7fa9fc43..f1265cb4 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/tcpip_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/tcpip_test.go @@ -1,3 +1,7 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package ssh import ( diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal.go similarity index 65% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal.go index 86853d6b..741eeb13 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal.go @@ -5,6 +5,7 @@ package terminal import ( + "bytes" "io" "sync" "unicode/utf8" @@ -53,7 +54,7 @@ type Terminal struct { lock sync.Mutex c io.ReadWriter - prompt string + prompt []rune // line is the current line being entered. line []rune @@ -61,6 +62,9 @@ type Terminal struct { pos int // echo is true if local echo is enabled echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool // cursorX contains the current X value of the cursor where the left // edge is 0. cursorY contains the row number where the first row of @@ -98,7 +102,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { return &Terminal{ Escape: &vt100EscapeCodes, c: c, - prompt: prompt, + prompt: []rune(prompt), termWidth: 80, termHeight: 24, echo: true, @@ -108,6 +112,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { const ( keyCtrlD = 4 + keyCtrlU = 21 keyEnter = '\r' keyEscape = 27 keyBackspace = 127 @@ -122,26 +127,36 @@ const ( keyEnd keyDeleteWord keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd ) +var pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} +var pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} + // bytesToKey tries to parse a key sequence from b. If successful, it returns // the key and the remainder of the input. Otherwise it returns utf8.RuneError. -func bytesToKey(b []byte) (rune, []byte) { +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { if len(b) == 0 { return utf8.RuneError, nil } - switch b[0] { - case 1: // ^A - return keyHome, b[1:] - case 5: // ^E - return keyEnd, b[1:] - case 8: // ^H - return keyBackspace, b[1:] - case 11: // ^K - return keyDeleteLine, b[1:] - case 23: // ^W - return keyDeleteWord, b[1:] + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } } if b[0] != keyEscape { @@ -152,7 +167,7 @@ func bytesToKey(b []byte) (rune, []byte) { return r, b[l:] } - if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { switch b[2] { case 'A': return keyUp, b[3:] @@ -162,11 +177,6 @@ func bytesToKey(b []byte) (rune, []byte) { return keyRight, b[3:] case 'D': return keyLeft, b[3:] - } - } - - if len(b) >= 3 && b[0] == keyEscape && b[1] == 'O' { - switch b[2] { case 'H': return keyHome, b[3:] case 'F': @@ -174,7 +184,7 @@ func bytesToKey(b []byte) (rune, []byte) { } } - if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { switch b[5] { case 'C': return keyAltRight, b[6:] @@ -183,12 +193,20 @@ func bytesToKey(b []byte) (rune, []byte) { } } + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + // If we get here then we have a key that we don't recognise, or a // partial sequence. It's not clear how one should find the end of a - // sequence without knowing them all, but it seems that [a-zA-Z] only + // sequence without knowing them all, but it seems that [a-zA-Z~] only // appears at the end of a sequence. for i, c := range b[0:] { - if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { return keyUnknown, b[i+1:] } } @@ -216,7 +234,7 @@ func (t *Terminal) moveCursorToPos(pos int) { return } - x := len(t.prompt) + pos + x := visualLength(t.prompt) + pos y := x / t.termWidth x = x % t.termWidth @@ -296,6 +314,29 @@ func (t *Terminal) setLine(newLine []rune, newPos int) { t.pos = newPos } +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\n') + } +} + func (t *Terminal) eraseNPreviousChars(n int) { if n == 0 { return @@ -314,7 +355,7 @@ func (t *Terminal) eraseNPreviousChars(n int) { for i := 0; i < n; i++ { t.queue(space) } - t.cursorX += n + t.advanceCursor(n) t.moveCursorToPos(t.pos) } } @@ -363,9 +404,35 @@ func (t *Terminal) countToRightWord() int { return pos - t.pos } +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + } + } + + return length +} + // handleKey processes the given key and, optionally, returns a line of text // that the user has entered. func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + switch key { case keyBackspace: if t.pos == 0 { @@ -449,10 +516,27 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) { // end of line. for i := t.pos; i < len(t.line); i++ { t.queue(space) - t.cursorX++ + t.advanceCursor(1) } t.line = t.line[:t.pos] t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) default: if t.AutoCompleteCallback != nil { prefix := string(t.line[:t.pos]) @@ -473,23 +557,29 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) { if len(t.line) == maxLineLength { return } - if len(t.line) == cap(t.line) { - newLine := make([]rune, len(t.line), 2*(1+len(t.line))) - copy(newLine, t.line) - t.line = newLine - } - t.line = t.line[:len(t.line)+1] - copy(t.line[t.pos+1:], t.line[t.pos:]) - t.line[t.pos] = key - if t.echo { - t.writeLine(t.line[t.pos:]) - } - t.pos++ - t.moveCursorToPos(t.pos) + t.addKeyToLine(key) } return } +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + func (t *Terminal) writeLine(line []rune) { for len(line) != 0 { remainingOnLine := t.termWidth - t.cursorX @@ -498,16 +588,8 @@ func (t *Terminal) writeLine(line []rune) { todo = remainingOnLine } t.queue(line[:todo]) - t.cursorX += todo + t.advanceCursor(visualLength(line[:todo])) line = line[todo:] - - if t.cursorX == t.termWidth { - t.cursorX = 0 - t.cursorY++ - if t.cursorY > t.maxLine { - t.maxLine = t.cursorY - } - } } } @@ -542,14 +624,11 @@ func (t *Terminal) Write(buf []byte) (n int, err error) { return } - t.queue([]rune(t.prompt)) - chars := len(t.prompt) + t.writeLine(t.prompt) if t.echo { - t.queue(t.line) - chars += len(t.line) + t.writeLine(t.line) } - t.cursorX = chars % t.termWidth - t.cursorY = chars / t.termWidth + t.moveCursorToPos(t.pos) if _, err = t.c.Write(t.outBuf); err != nil { @@ -566,7 +645,7 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) { defer t.lock.Unlock() oldPrompt := t.prompt - t.prompt = prompt + t.prompt = []rune(prompt) t.echo = false line, err = t.readLine() @@ -589,22 +668,41 @@ func (t *Terminal) readLine() (line string, err error) { // t.lock must be held at this point if t.cursorX == 0 && t.cursorY == 0 { - t.writeLine([]rune(t.prompt)) + t.writeLine(t.prompt) t.c.Write(t.outBuf) t.outBuf = t.outBuf[:0] } + lineIsPasted := t.pasteActive + for { rest := t.remainder lineOk := false for !lineOk { var key rune - key, rest = bytesToKey(rest) + key, rest = bytesToKey(rest, t.pasteActive) if key == utf8.RuneError { break } - if key == keyCtrlD { - return "", io.EOF + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false } line, lineOk = t.handleKey(key) } @@ -621,6 +719,9 @@ func (t *Terminal) readLine() (line string, err error) { t.historyIndex = -1 t.history.Add(line) } + if lineIsPasted { + err = ErrPasteIndicator + } return } @@ -648,14 +749,106 @@ func (t *Terminal) SetPrompt(prompt string) { t.lock.Lock() defer t.lock.Unlock() - t.prompt = prompt + t.prompt = []rune(prompt) } -func (t *Terminal) SetSize(width, height int) { +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { t.lock.Lock() defer t.lock.Unlock() + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } } // stRingBuffer is a ring buffer of strings. diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal_test.go similarity index 73% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal_test.go index 641576c8..a663fe41 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/terminal_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/terminal_test.go @@ -163,6 +163,40 @@ var keyPressTests = []struct { line: "£", throwAwayLines: 1, }, + { + // Ctrl-D at the end of the line should be ignored. + in: "a\004\r", + line: "a", + }, + { + // a, b, left, Ctrl-D should erase the b. + in: "ab\x1b[D\004\r", + line: "a", + }, + { + // a, b, c, d, left, left, ^U should erase to the beginning of + // the line. + in: "abcd\x1b[D\x1b[D\025\r", + line: "cd", + }, + { + // Bracketed paste mode: control sequences should be returned + // verbatim in paste mode. + in: "abc\x1b[200~de\177f\x1b[201~\177\r", + line: "abcde\177", + }, + { + // Enter in bracketed paste mode should still work. + in: "abc\x1b[200~d\refg\x1b[201~h\r", + line: "efgh", + throwAwayLines: 1, + }, + { + // Lines consisting entirely of pasted data should be indicated as such. + in: "\x1b[200~a\r", + line: "a", + err: ErrPasteIndicator, + }, } func TestKeyPresses(t *testing.T) { @@ -207,3 +241,29 @@ func TestPasswordNotSaved(t *testing.T) { t.Fatalf("password was saved in history") } } + +var setSizeTests = []struct { + width, height int +}{ + {40, 13}, + {80, 24}, + {132, 43}, +} + +func TestTerminalSetSize(t *testing.T) { + for _, setSize := range setSizeTests { + c := &MockTerminal{ + toSend: []byte("password\r\x1b[A\r"), + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + ss.SetSize(setSize.width, setSize.height) + pw, _ := ss.ReadPassword("Password: ") + if pw != "password" { + t.Fatalf("failed to read password, got %s", pw) + } + if string(c.received) != "Password: \r\n" { + t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util.go similarity index 96% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util.go index 8df94f5d..598e3df7 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux,!appengine darwin +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. @@ -14,7 +14,7 @@ // panic(err) // } // defer terminal.Restore(0, oldState) -package terminal +package terminal // import "golang.org/x/crypto/ssh/terminal" import ( "io" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_bsd.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_bsd.go similarity index 84% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_bsd.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_bsd.go index 1654453b..9c1ffd14 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/terminal/util_bsd.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin +// +build darwin dragonfly freebsd netbsd openbsd package terminal diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_linux.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_linux.go new file mode 100644 index 00000000..5883b22d --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_linux.go @@ -0,0 +1,11 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_windows.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_windows.go new file mode 100644 index 00000000..2dd6c3d9 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -0,0 +1,174 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + short int16 + word uint16 + + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + st &^= (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/agent_unix_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/agent_unix_test.go new file mode 100644 index 00000000..502e24fe --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/agent_unix_test.go @@ -0,0 +1,50 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux netbsd openbsd + +package test + +import ( + "bytes" + "testing" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +func TestAgentForward(t *testing.T) { + server := newServer(t) + defer server.Shutdown() + conn := server.Dial(clientConfig()) + defer conn.Close() + + keyring := agent.NewKeyring() + keyring.Add(testPrivateKeys["dsa"], nil, "") + pub := testPublicKeys["dsa"] + + sess, err := conn.NewSession() + if err != nil { + t.Fatalf("NewSession: %v", err) + } + if err := agent.RequestAgentForwarding(sess); err != nil { + t.Fatalf("RequestAgentForwarding: %v", err) + } + + if err := agent.ForwardToAgent(conn, keyring); err != nil { + t.Fatalf("SetupForwardKeyring: %v", err) + } + out, err := sess.CombinedOutput("ssh-add -L") + if err != nil { + t.Fatalf("running ssh-add: %v, out %s", err, out) + } + key, _, _, _, err := ssh.ParseAuthorizedKey(out) + if err != nil { + t.Fatalf("ParseAuthorizedKey(%q): %v", out, err) + } + + if !bytes.Equal(key.Marshal(), pub.Marshal()) { + t.Fatalf("got key %s, want %s", ssh.MarshalAuthorizedKey(key), ssh.MarshalAuthorizedKey(pub)) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/cert_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/cert_test.go new file mode 100644 index 00000000..364790f1 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/cert_test.go @@ -0,0 +1,47 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux netbsd openbsd + +package test + +import ( + "crypto/rand" + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestCertLogin(t *testing.T) { + s := newServer(t) + defer s.Shutdown() + + // Use a key different from the default. + clientKey := testSigners["dsa"] + caAuthKey := testSigners["ecdsa"] + cert := &ssh.Certificate{ + Key: clientKey.PublicKey(), + ValidPrincipals: []string{username()}, + CertType: ssh.UserCert, + ValidBefore: ssh.CertTimeInfinity, + } + if err := cert.SignCert(rand.Reader, caAuthKey); err != nil { + t.Fatalf("SetSignature: %v", err) + } + + certSigner, err := ssh.NewCertSigner(cert, clientKey) + if err != nil { + t.Fatalf("NewCertSigner: %v", err) + } + + conf := &ssh.ClientConfig{ + User: username(), + } + conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner)) + client, err := s.TryDial(conf) + if err != nil { + t.Fatalf("TryDial: %v", err) + } + client.Close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/doc.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/doc.go similarity index 82% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/doc.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/doc.go index 787b8fa2..d21d6b71 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/doc.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/doc.go @@ -4,4 +4,4 @@ // This package contains integration tests for the // code.google.com/p/go.crypto/ssh package. -package test +package test // import "golang.org/x/crypto/ssh/test" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/forward_unix_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/forward_unix_test.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/forward_unix_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/forward_unix_test.go index 3a57c100..877a88cd 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/forward_unix_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/forward_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd plan9 +// +build darwin dragonfly freebsd linux netbsd openbsd package test diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/session_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/session_test.go similarity index 55% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/session_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/session_test.go index bd7307dd..0b7892ba 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/session_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/session_test.go @@ -10,7 +10,8 @@ package test import ( "bytes" - "code.google.com/p/go.crypto/ssh" + "errors" + "golang.org/x/crypto/ssh" "io" "strings" "testing" @@ -38,12 +39,13 @@ func TestHostKeyCheck(t *testing.T) { defer server.Shutdown() conf := clientConfig() - k := conf.HostKeyChecker.(*storedHostKey) + hostDB := hostKeyDB() + conf.HostKeyCallback = hostDB.Check // change the keys. - k.keys[ssh.KeyAlgoRSA][25]++ - k.keys[ssh.KeyAlgoDSA][25]++ - k.keys[ssh.KeyAlgoECDSA256][25]++ + hostDB.keys[ssh.KeyAlgoRSA][25]++ + hostDB.keys[ssh.KeyAlgoDSA][25]++ + hostDB.keys[ssh.KeyAlgoECDSA256][25]++ conn, err := server.TryDial(conf) if err == nil { @@ -54,6 +56,53 @@ func TestHostKeyCheck(t *testing.T) { } } +func TestRunCommandStdin(t *testing.T) { + server := newServer(t) + defer server.Shutdown() + conn := server.Dial(clientConfig()) + defer conn.Close() + + session, err := conn.NewSession() + if err != nil { + t.Fatalf("session failed: %v", err) + } + defer session.Close() + + r, w := io.Pipe() + defer r.Close() + defer w.Close() + session.Stdin = r + + err = session.Run("true") + if err != nil { + t.Fatalf("session failed: %v", err) + } +} + +func TestRunCommandStdinError(t *testing.T) { + server := newServer(t) + defer server.Shutdown() + conn := server.Dial(clientConfig()) + defer conn.Close() + + session, err := conn.NewSession() + if err != nil { + t.Fatalf("session failed: %v", err) + } + defer session.Close() + + r, w := io.Pipe() + defer r.Close() + session.Stdin = r + pipeErr := errors.New("closing write end of pipe") + w.CloseWithError(pipeErr) + + err = session.Run("true") + if err != pipeErr { + t.Fatalf("expected %v, found %v", pipeErr, err) + } +} + func TestRunCommandFailed(t *testing.T) { server := newServer(t) defer server.Shutdown() @@ -107,7 +156,7 @@ func TestFuncLargeRead(t *testing.T) { t.Fatalf("unable to acquire stdout pipe: %s", err) } - err = session.Start("dd if=/dev/urandom bs=2048 count=1") + err = session.Start("dd if=/dev/urandom bs=2048 count=1024") if err != nil { t.Fatalf("unable to execute remote command: %s", err) } @@ -118,11 +167,53 @@ func TestFuncLargeRead(t *testing.T) { t.Fatalf("error reading from remote stdout: %s", err) } - if n != 2048 { + if n != 2048*1024 { t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n) } } +func TestKeyChange(t *testing.T) { + server := newServer(t) + defer server.Shutdown() + conf := clientConfig() + hostDB := hostKeyDB() + conf.HostKeyCallback = hostDB.Check + conf.RekeyThreshold = 1024 + conn := server.Dial(conf) + defer conn.Close() + + for i := 0; i < 4; i++ { + session, err := conn.NewSession() + if err != nil { + t.Fatalf("unable to create new session: %s", err) + } + + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("unable to acquire stdout pipe: %s", err) + } + + err = session.Start("dd if=/dev/urandom bs=1024 count=1") + if err != nil { + t.Fatalf("unable to execute remote command: %s", err) + } + buf := new(bytes.Buffer) + n, err := io.Copy(buf, stdout) + if err != nil { + t.Fatalf("error reading from remote stdout: %s", err) + } + + want := int64(1024) + if n != want { + t.Fatalf("Expected %d bytes but read only %d from remote command", want, n) + } + } + + if changes := hostDB.checkCount; changes < 4 { + t.Errorf("got %d key changes, want 4", changes) + } +} + func TestInvalidTerminalMode(t *testing.T) { server := newServer(t) defer server.Shutdown() @@ -183,3 +274,44 @@ func TestValidTerminalMode(t *testing.T) { t.Fatalf("terminal mode failure: expected -echo in stty output, got %s", sttyOutput) } } + +func TestCiphers(t *testing.T) { + var config ssh.Config + config.SetDefaults() + cipherOrder := config.Ciphers + + for _, ciph := range cipherOrder { + server := newServer(t) + defer server.Shutdown() + conf := clientConfig() + conf.Ciphers = []string{ciph} + // Don't fail if sshd doesnt have the cipher. + conf.Ciphers = append(conf.Ciphers, cipherOrder...) + conn, err := server.TryDial(conf) + if err == nil { + conn.Close() + } else { + t.Fatalf("failed for cipher %q", ciph) + } + } +} + +func TestMACs(t *testing.T) { + var config ssh.Config + config.SetDefaults() + macOrder := config.MACs + + for _, mac := range macOrder { + server := newServer(t) + defer server.Shutdown() + conf := clientConfig() + conf.MACs = []string{mac} + // Don't fail if sshd doesnt have the MAC. + conf.MACs = append(conf.MACs, macOrder...) + if conn, err := server.TryDial(conf); err == nil { + conn.Close() + } else { + t.Fatalf("failed for MAC %q", mac) + } + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/tcpip_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/tcpip_test.go new file mode 100644 index 00000000..a2eb9358 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/tcpip_test.go @@ -0,0 +1,46 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows + +package test + +// direct-tcpip functional tests + +import ( + "io" + "net" + "testing" +) + +func TestDial(t *testing.T) { + server := newServer(t) + defer server.Shutdown() + sshConn := server.Dial(clientConfig()) + defer sshConn.Close() + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Listen: %v", err) + } + defer l.Close() + + go func() { + for { + c, err := l.Accept() + if err != nil { + break + } + + io.WriteString(c, c.RemoteAddr().String()) + c.Close() + } + }() + + conn, err := sshConn.Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer conn.Close() +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/test_unix_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/test_unix_test.go similarity index 63% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/test_unix_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/test_unix_test.go index 86df3f48..f1fc50b2 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/test/test_unix_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/test_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd plan9 +// +build darwin dragonfly freebsd linux netbsd openbsd plan9 package test @@ -11,7 +11,6 @@ package test import ( "bytes" "fmt" - "io" "io/ioutil" "log" "net" @@ -22,14 +21,15 @@ import ( "testing" "text/template" - "code.google.com/p/go.crypto/ssh" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" ) const sshd_config = ` Protocol 2 -HostKey {{.Dir}}/ssh_host_rsa_key -HostKey {{.Dir}}/ssh_host_dsa_key -HostKey {{.Dir}}/ssh_host_ecdsa_key +HostKey {{.Dir}}/id_rsa +HostKey {{.Dir}}/id_dsa +HostKey {{.Dir}}/id_ecdsa Pidfile {{.Dir}}/sshd.pid #UsePrivilegeSeparation no KeyRegenerationInterval 3600 @@ -41,41 +41,14 @@ PermitRootLogin no StrictModes no RSAAuthentication yes PubkeyAuthentication yes -AuthorizedKeysFile {{.Dir}}/authorized_keys +AuthorizedKeysFile {{.Dir}}/id_user.pub +TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub IgnoreRhosts yes RhostsRSAAuthentication no HostbasedAuthentication no ` -var ( - configTmpl template.Template - privateKey ssh.Signer - hostKeyRSA ssh.Signer - hostKeyECDSA ssh.Signer - hostKeyDSA ssh.Signer -) - -func init() { - template.Must(configTmpl.Parse(sshd_config)) - - for n, k := range map[string]*ssh.Signer{ - "ssh_host_ecdsa_key": &hostKeyECDSA, - "ssh_host_rsa_key": &hostKeyRSA, - "ssh_host_dsa_key": &hostKeyDSA, - } { - var err error - *k, err = ssh.ParsePrivateKey([]byte(keys[n])) - if err != nil { - panic(fmt.Sprintf("ParsePrivateKey(%q): %v", n, err)) - } - } - - var err error - privateKey, err = ssh.ParsePrivateKey([]byte(testClientPrivateKey)) - if err != nil { - panic(fmt.Sprintf("ParsePrivateKey: %v", err)) - } -} +var configTmpl = template.Must(template.New("").Parse(sshd_config)) type server struct { t *testing.T @@ -107,36 +80,44 @@ func username() string { type storedHostKey struct { // keys map from an algorithm string to binary key data. keys map[string][]byte + + // checkCount counts the Check calls. Used for testing + // rekeying. + checkCount int } func (k *storedHostKey) Add(key ssh.PublicKey) { if k.keys == nil { k.keys = map[string][]byte{} } - k.keys[key.PublicKeyAlgo()] = ssh.MarshalPublicKey(key) + k.keys[key.Type()] = key.Marshal() } -func (k *storedHostKey) Check(addr string, remote net.Addr, algo string, key []byte) error { - if k.keys == nil || bytes.Compare(key, k.keys[algo]) != 0 { +func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error { + k.checkCount++ + algo := key.Type() + + if k.keys == nil || bytes.Compare(key.Marshal(), k.keys[algo]) != 0 { return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo]) } return nil } -func clientConfig() *ssh.ClientConfig { - keyChecker := storedHostKey{} - keyChecker.Add(hostKeyECDSA.PublicKey()) - keyChecker.Add(hostKeyRSA.PublicKey()) - keyChecker.Add(hostKeyDSA.PublicKey()) +func hostKeyDB() *storedHostKey { + keyChecker := &storedHostKey{} + keyChecker.Add(testPublicKeys["ecdsa"]) + keyChecker.Add(testPublicKeys["rsa"]) + keyChecker.Add(testPublicKeys["dsa"]) + return keyChecker +} - kc := new(keychain) - kc.keys = append(kc.keys, privateKey) +func clientConfig() *ssh.ClientConfig { config := &ssh.ClientConfig{ User: username(), - Auth: []ssh.ClientAuth{ - ssh.ClientAuthKeyring(kc), + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(testSigners["user"]), }, - HostKeyChecker: &keyChecker, + HostKeyCallback: hostKeyDB().Check, } return config } @@ -171,7 +152,7 @@ func unixConnection() (*net.UnixConn, *net.UnixConn, error) { return c1.(*net.UnixConn), c2.(*net.UnixConn), nil } -func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) { +func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) { sshd, err := exec.LookPath("sshd") if err != nil { s.t.Skipf("skipping test: %v", err) @@ -197,10 +178,14 @@ func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.ClientConn, error) { s.t.Fatalf("s.cmd.Start: %v", err) } s.clientConn = c1 - return ssh.Client(c1, config) + conn, chans, reqs, err := ssh.NewClientConn(c1, "", config) + if err != nil { + return nil, err + } + return ssh.NewClient(conn, chans, reqs), nil } -func (s *server) Dial(config *ssh.ClientConfig) *ssh.ClientConn { +func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client { conn, err := s.TryDial(config) if err != nil { s.t.Fail() @@ -226,8 +211,22 @@ func (s *server) Shutdown() { s.cleanup() } +func writeFile(path string, contents []byte) { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if err != nil { + panic(err) + } + defer f.Close() + if _, err := f.Write(contents); err != nil { + panic(err) + } +} + // newServer returns a new mock ssh server. func newServer(t *testing.T) *server { + if testing.Short() { + t.Skip("skipping test due to -short") + } dir, err := ioutil.TempDir("", "sshtest") if err != nil { t.Fatal(err) @@ -244,15 +243,10 @@ func newServer(t *testing.T) *server { } f.Close() - for k, v := range keys { - f, err := os.OpenFile(filepath.Join(dir, k), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) - if err != nil { - t.Fatal(err) - } - if _, err := f.Write([]byte(v)); err != nil { - t.Fatal(err) - } - f.Close() + for k, v := range testdata.PEMBytes { + filename := "id_" + k + writeFile(filepath.Join(dir, filename), v) + writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k])) } return &server{ @@ -265,32 +259,3 @@ func newServer(t *testing.T) *server { }, } } - -// keychain implements the ClientKeyring interface. -type keychain struct { - keys []ssh.Signer -} - -func (k *keychain) Key(i int) (ssh.PublicKey, error) { - if i < 0 || i >= len(k.keys) { - return nil, nil - } - return k.keys[i].PublicKey(), nil -} - -func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) { - return k.keys[i].Sign(rand, data) -} - -func (k *keychain) loadPEM(file string) error { - buf, err := ioutil.ReadFile(file) - if err != nil { - return err - } - key, err := ssh.ParsePrivateKey(buf) - if err != nil { - return err - } - k.keys = append(k.keys, key) - return nil -} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/testdata_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/testdata_test.go new file mode 100644 index 00000000..ae48c751 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/test/testdata_test.go @@ -0,0 +1,64 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package test + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]ssh.Signer + testPublicKeys map[string]ssh.PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]ssh.Signer, n) + testPublicKeys = make(map[string]ssh.PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &ssh.Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/doc.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/doc.go new file mode 100644 index 00000000..3f4d74d9 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/doc.go @@ -0,0 +1,8 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This package contains test data shared between the various subpackages of +// the code.google.com/p/go.crypto/ssh package. Under no circumstance should +// this data be used for production code. +package testdata // import "golang.org/x/crypto/ssh/testdata" diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/keys.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/keys.go new file mode 100644 index 00000000..5ff1c0e0 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata/keys.go @@ -0,0 +1,43 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testdata + +var PEMBytes = map[string][]byte{ + "dsa": []byte(`-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQD6PDSEyXiI9jfNs97WuM46MSDCYlOqWw80ajN16AohtBncs1YB +lHk//dQOvCYOsYaE+gNix2jtoRjwXhDsc25/IqQbU1ahb7mB8/rsaILRGIbA5WH3 +EgFtJmXFovDz3if6F6TzvhFpHgJRmLYVR8cqsezL3hEZOvvs2iH7MorkxwIVAJHD +nD82+lxh2fb4PMsIiaXudAsBAoGAQRf7Q/iaPRn43ZquUhd6WwvirqUj+tkIu6eV +2nZWYmXLlqFQKEy4Tejl7Wkyzr2OSYvbXLzo7TNxLKoWor6ips0phYPPMyXld14r +juhT24CrhOzuLMhDduMDi032wDIZG4Y+K7ElU8Oufn8Sj5Wge8r6ANmmVgmFfynr +FhdYCngCgYEA3ucGJ93/Mx4q4eKRDxcWD3QzWyqpbRVRRV1Vmih9Ha/qC994nJFz +DQIdjxDIT2Rk2AGzMqFEB68Zc3O+Wcsmz5eWWzEwFxaTwOGWTyDqsDRLm3fD+QYj +nOwuxb0Kce+gWI8voWcqC9cyRm09jGzu2Ab3Bhtpg8JJ8L7gS3MRZK4CFEx4UAfY +Fmsr0W6fHB9nhS4/UXM8 +-----END DSA PRIVATE KEY----- +`), + "ecdsa": []byte(`-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49 +AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ +6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA== +-----END EC PRIVATE KEY----- +`), + "rsa": []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld +r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ +tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC +nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW +2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB +y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr +rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg== +-----END RSA PRIVATE KEY----- +`), + "user": []byte(`-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49 +AwEHoUQDQgAEYcO2xNKiRUYOLEHM7VYAp57HNyKbOdYtHD83Z4hzNPVC4tM5mdGD +PLL8IEwvYu2wq+lpXfGQnNMbzYf9gspG0w== +-----END EC PRIVATE KEY----- +`), +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata_test.go new file mode 100644 index 00000000..f2828c1b --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/testdata_test.go @@ -0,0 +1,63 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package ssh + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]Signer + testPublicKeys map[string]PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]Signer, n) + testPublicKeys = make(map[string]PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport.go new file mode 100644 index 00000000..4f68b047 --- /dev/null +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport.go @@ -0,0 +1,327 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bufio" + "errors" + "io" +) + +const ( + gcmCipherID = "aes128-gcm@openssh.com" +) + +// packetConn represents a transport that implements packet based +// operations. +type packetConn interface { + // Encrypt and send a packet of data to the remote peer. + writePacket(packet []byte) error + + // Read a packet from the connection + readPacket() ([]byte, error) + + // Close closes the write-side of the connection. + Close() error +} + +// transport is the keyingTransport that implements the SSH packet +// protocol. +type transport struct { + reader connectionState + writer connectionState + + bufReader *bufio.Reader + bufWriter *bufio.Writer + rand io.Reader + + io.Closer + + // Initial H used for the session ID. Once assigned this does + // not change, even during subsequent key exchanges. + sessionID []byte +} + +func (t *transport) getSessionID() []byte { + if t.sessionID == nil { + panic("session ID not set yet") + } + s := make([]byte, len(t.sessionID)) + copy(s, t.sessionID) + return s +} + +// packetCipher represents a combination of SSH encryption/MAC +// protocol. A single instance should be used for one direction only. +type packetCipher interface { + // writePacket encrypts the packet and writes it to w. The + // contents of the packet are generally scrambled. + writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error + + // readPacket reads and decrypts a packet of data. The + // returned packet may be overwritten by future calls of + // readPacket. + readPacket(seqnum uint32, r io.Reader) ([]byte, error) +} + +// connectionState represents one side (read or write) of the +// connection. This is necessary because each direction has its own +// keys, and can even have its own algorithms +type connectionState struct { + packetCipher + seqNum uint32 + dir direction + pendingKeyChange chan packetCipher +} + +// prepareKeyChange sets up key material for a keychange. The key changes in +// both directions are triggered by reading and writing a msgNewKey packet +// respectively. +func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { + if t.sessionID == nil { + t.sessionID = kexResult.H + } + + kexResult.SessionID = t.sessionID + + if ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult); err != nil { + return err + } else { + t.reader.pendingKeyChange <- ciph + } + + if ciph, err := newPacketCipher(t.writer.dir, algs.w, kexResult); err != nil { + return err + } else { + t.writer.pendingKeyChange <- ciph + } + + return nil +} + +// Read and decrypt next packet. +func (t *transport) readPacket() ([]byte, error) { + return t.reader.readPacket(t.bufReader) +} + +func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { + packet, err := s.packetCipher.readPacket(s.seqNum, r) + s.seqNum++ + if err == nil && len(packet) == 0 { + err = errors.New("ssh: zero length packet") + } + + if len(packet) > 0 && packet[0] == msgNewKeys { + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + return nil, errors.New("ssh: got bogus newkeys message.") + } + } + + // The packet may point to an internal buffer, so copy the + // packet out here. + fresh := make([]byte, len(packet)) + copy(fresh, packet) + + return fresh, err +} + +func (t *transport) writePacket(packet []byte) error { + return t.writer.writePacket(t.bufWriter, t.rand, packet) +} + +func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error { + changeKeys := len(packet) > 0 && packet[0] == msgNewKeys + + err := s.packetCipher.writePacket(s.seqNum, w, rand, packet) + if err != nil { + return err + } + if err = w.Flush(); err != nil { + return err + } + s.seqNum++ + if changeKeys { + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + panic("ssh: no key material for msgNewKeys") + } + } + return err +} + +func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport { + t := &transport{ + bufReader: bufio.NewReader(rwc), + bufWriter: bufio.NewWriter(rwc), + rand: rand, + reader: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + writer: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + Closer: rwc, + } + if isClient { + t.reader.dir = serverKeys + t.writer.dir = clientKeys + } else { + t.reader.dir = clientKeys + t.writer.dir = serverKeys + } + + return t +} + +type direction struct { + ivTag []byte + keyTag []byte + macKeyTag []byte +} + +var ( + serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} + clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} +) + +// generateKeys generates key material for IV, MAC and encryption. +func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) { + cipherMode := cipherModes[algs.Cipher] + macMode := macModes[algs.MAC] + + iv = make([]byte, cipherMode.ivSize) + key = make([]byte, cipherMode.keySize) + macKey = make([]byte, macMode.keySize) + + generateKeyMaterial(iv, d.ivTag, kex) + generateKeyMaterial(key, d.keyTag, kex) + generateKeyMaterial(macKey, d.macKeyTag, kex) + return +} + +// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as +// described in RFC 4253, section 6.4. direction should either be serverKeys +// (to setup server->client keys) or clientKeys (for client->server keys). +func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { + iv, key, macKey := generateKeys(d, algs, kex) + + if algs.Cipher == gcmCipherID { + return newGCMCipher(iv, key, macKey) + } + + c := &streamPacketCipher{ + mac: macModes[algs.MAC].new(macKey), + } + c.macResult = make([]byte, c.mac.Size()) + + var err error + c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv) + if err != nil { + return nil, err + } + + return c, nil +} + +// generateKeyMaterial fills out with key material generated from tag, K, H +// and sessionId, as specified in RFC 4253, section 7.2. +func generateKeyMaterial(out, tag []byte, r *kexResult) { + var digestsSoFar []byte + + h := r.Hash.New() + for len(out) > 0 { + h.Reset() + h.Write(r.K) + h.Write(r.H) + + if len(digestsSoFar) == 0 { + h.Write(tag) + h.Write(r.SessionID) + } else { + h.Write(digestsSoFar) + } + + digest := h.Sum(nil) + n := copy(out, digest) + out = out[n:] + if len(out) > 0 { + digestsSoFar = append(digestsSoFar, digest...) + } + } +} + +const packageVersion = "SSH-2.0-Go" + +// Sends and receives a version line. The versionLine string should +// be US ASCII, start with "SSH-2.0-", and should not include a +// newline. exchangeVersions returns the other side's version line. +func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { + // Contrary to the RFC, we do not ignore lines that don't + // start with "SSH-2.0-" to make the library usable with + // nonconforming servers. + for _, c := range versionLine { + // The spec disallows non US-ASCII chars, and + // specifically forbids null chars. + if c < 32 { + return nil, errors.New("ssh: junk character in version line") + } + } + if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { + return + } + + them, err = readVersion(rw) + return them, err +} + +// maxVersionStringBytes is the maximum number of bytes that we'll +// accept as a version string. RFC 4253 section 4.2 limits this at 255 +// chars +const maxVersionStringBytes = 255 + +// Read version string as specified by RFC 4253, section 4.2. +func readVersion(r io.Reader) ([]byte, error) { + versionString := make([]byte, 0, 64) + var ok bool + var buf [1]byte + + for len(versionString) < maxVersionStringBytes { + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return nil, err + } + // The RFC says that the version should be terminated with \r\n + // but several SSH servers actually only send a \n. + if buf[0] == '\n' { + ok = true + break + } + + // non ASCII chars are disallowed, but we are lenient, + // since Go doesn't use null-terminated strings. + + // The RFC allows a comment after a space, however, + // all of it (version and comments) goes into the + // session hash. + versionString = append(versionString, buf[0]) + } + + if !ok { + return nil, errors.New("ssh: overflow reading version string") + } + + // There might be a '\r' on the end which we should remove. + if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { + versionString = versionString[:len(versionString)-1] + } + return versionString, nil +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport_test.go similarity index 64% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport_test.go index 33201146..92d83abf 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/ssh/transport_test.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/ssh/transport_test.go @@ -6,6 +6,8 @@ package ssh import ( "bytes" + "crypto/rand" + "encoding/binary" "strings" "testing" ) @@ -67,3 +69,41 @@ func TestExchangeVersions(t *testing.T) { } } } + +type closerBuffer struct { + bytes.Buffer +} + +func (b *closerBuffer) Close() error { + return nil +} + +func TestTransportMaxPacketWrite(t *testing.T) { + buf := &closerBuffer{} + tr := newTransport(buf, rand.Reader, true) + huge := make([]byte, maxPacket+1) + err := tr.writePacket(huge) + if err == nil { + t.Errorf("transport accepted write for a huge packet.") + } +} + +func TestTransportMaxPacketReader(t *testing.T) { + var header [5]byte + huge := make([]byte, maxPacket+128) + binary.BigEndian.PutUint32(header[0:], uint32(len(huge))) + // padding. + header[4] = 0 + + buf := &closerBuffer{} + buf.Write(header[:]) + buf.Write(huge) + + tr := newTransport(buf, rand.Reader, true) + _, err := tr.readPacket() + if err == nil { + t.Errorf("transport succeeded reading huge packet.") + } else if !strings.Contains(err.Error(), "large") { + t.Errorf("got %q, should mention %q", err.Error(), "large") + } +} diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/twofish/twofish.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/twofish/twofish.go similarity index 99% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/twofish/twofish.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/twofish/twofish.go index a930218c..376fa0ec 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/twofish/twofish.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/twofish/twofish.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package twofish implements Bruce Schneier's Twofish encryption algorithm. -package twofish +package twofish // import "golang.org/x/crypto/twofish" // Twofish is defined in http://www.schneier.com/paper-twofish-paper.pdf [TWOFISH] diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/twofish/twofish_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/twofish/twofish_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/twofish/twofish_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/twofish/twofish_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/block.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/block.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/block.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/block.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/cipher.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/cipher.go similarity index 97% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/cipher.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/cipher.go index 0e10b8e5..108b4263 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/cipher.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/cipher.go @@ -4,7 +4,7 @@ // Package xtea implements XTEA encryption, as defined in Needham and Wheeler's // 1997 technical report, "Tea extensions." -package xtea +package xtea // import "golang.org/x/crypto/xtea" // For details, see http://www.cix.co.uk/~klockstone/xtea.pdf diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/xtea_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/xtea_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xtea/xtea_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xtea/xtea_test.go diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xts/xts.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xts/xts.go similarity index 98% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xts/xts.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xts/xts.go index f0af79da..c9a283b2 100644 --- a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xts/xts.go +++ b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xts/xts.go @@ -19,7 +19,7 @@ // // (Note: this package does not implement ciphertext-stealing so sectors must // be a multiple of 16 bytes.) -package xts +package xts // import "golang.org/x/crypto/xts" import ( "crypto/cipher" diff --git a/src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xts/xts_test.go b/src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xts/xts_test.go similarity index 100% rename from src/github.com/smira/aptly/_vendor/src/code.google.com/p/go.crypto/xts/xts_test.go rename to src/github.com/smira/aptly/_vendor/src/golang.org/x/crypto/xts/xts_test.go diff --git a/src/github.com/smira/aptly/api/api.go b/src/github.com/smira/aptly/api/api.go new file mode 100644 index 00000000..cadaae6d --- /dev/null +++ b/src/github.com/smira/aptly/api/api.go @@ -0,0 +1,113 @@ +// Package api provides implementation of aptly REST API +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/query" + "sort" + "time" +) + +// Lock order acquisition (canonical): +// 1. RemoteRepoCollection +// 2. LocalRepoCollection +// 3. SnapshotCollection +// 4. PublishedRepoCollection + +// GET /api/version +func apiVersion(c *gin.Context) { + c.JSON(200, gin.H{"Version": aptly.Version}) +} + +// Periodically flushes CollectionFactory to free up memory used by collections, +// flushing caches. +// +// Should be run in goroutine! +func cacheFlusher() { + ticker := time.Tick(15 * time.Minute) + + for { + <-ticker + + // lock everything to eliminate in-progress calls + r := context.CollectionFactory().RemoteRepoCollection() + r.Lock() + defer r.Unlock() + + l := context.CollectionFactory().LocalRepoCollection() + l.Lock() + defer l.Unlock() + + s := context.CollectionFactory().SnapshotCollection() + s.Lock() + defer s.Unlock() + + p := context.CollectionFactory().PublishedRepoCollection() + p.Lock() + defer p.Unlock() + + // all collections locked, flush them + context.CollectionFactory().Flush() + } +} + +// Common piece of code to show list of packages, +// with searching & details if requested +func showPackages(c *gin.Context, reflist *deb.PackageRefList) { + result := []*deb.Package{} + + list, err := deb.NewPackageListFromRefList(reflist, context.CollectionFactory().PackageCollection(), nil) + if err != nil { + c.Fail(404, err) + return + } + + queryS := c.Request.URL.Query().Get("q") + if queryS != "" { + q, err := query.Parse(c.Request.URL.Query().Get("q")) + if err != nil { + c.Fail(400, err) + return + } + + withDeps := c.Request.URL.Query().Get("withDeps") == "1" + architecturesList := []string{} + + if withDeps { + if len(context.ArchitecturesList()) > 0 { + architecturesList = context.ArchitecturesList() + } else { + architecturesList = list.Architectures(false) + } + + sort.Strings(architecturesList) + + if len(architecturesList) == 0 { + c.Fail(400, fmt.Errorf("unable to determine list of architectures, please specify explicitly")) + return + } + } + + list.PrepareIndex() + + list, err = list.Filter([]deb.PackageQuery{q}, withDeps, + nil, context.DependencyOptions(), architecturesList) + if err != nil { + c.Fail(500, fmt.Errorf("unable to search: %s", err)) + } + } + + if c.Request.URL.Query().Get("format") == "details" { + list.ForEach(func(p *deb.Package) error { + result = append(result, p) + return nil + }) + + c.JSON(200, result) + } else { + c.JSON(200, list.Strings()) + } +} diff --git a/src/github.com/smira/aptly/api/files.go b/src/github.com/smira/aptly/api/files.go new file mode 100644 index 00000000..55fdb364 --- /dev/null +++ b/src/github.com/smira/aptly/api/files.go @@ -0,0 +1,185 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "io" + "os" + "path/filepath" + "strings" +) + +func verifyPath(path string) bool { + path = filepath.Clean(path) + for _, part := range strings.Split(path, string(filepath.Separator)) { + if part == ".." || part == "." { + return false + } + } + + return true + +} + +func verifyDir(c *gin.Context) bool { + if !verifyPath(c.Params.ByName("dir")) { + c.Fail(400, fmt.Errorf("wrong dir")) + return false + } + + return true +} + +// GET /files +func apiFilesListDirs(c *gin.Context) { + list := []string{} + + err := filepath.Walk(context.UploadPath(), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == context.UploadPath() { + return nil + } + + if info.IsDir() { + list = append(list, filepath.Base(path)) + return filepath.SkipDir + } + + return nil + }) + + if err != nil && !os.IsNotExist(err) { + c.Fail(400, err) + return + } + + c.JSON(200, list) +} + +// POST /files/:dir/ +func apiFilesUpload(c *gin.Context) { + if !verifyDir(c) { + return + } + + path := filepath.Join(context.UploadPath(), c.Params.ByName("dir")) + err := os.MkdirAll(path, 0777) + + if err != nil { + c.Fail(500, err) + return + } + + err = c.Request.ParseMultipartForm(10 * 1024 * 1024) + if err != nil { + c.Fail(400, err) + return + } + + stored := []string{} + + for _, files := range c.Request.MultipartForm.File { + for _, file := range files { + src, err := file.Open() + if err != nil { + c.Fail(500, err) + return + } + defer src.Close() + + destPath := filepath.Join(path, filepath.Base(file.Filename)) + dst, err := os.Create(destPath) + if err != nil { + c.Fail(500, err) + return + } + defer dst.Close() + + _, err = io.Copy(dst, src) + if err != nil { + c.Fail(500, err) + return + } + + stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename))) + } + } + + c.JSON(200, stored) + +} + +// GET /files/:dir +func apiFilesListFiles(c *gin.Context) { + if !verifyDir(c) { + return + } + + list := []string{} + root := filepath.Join(context.UploadPath(), c.Params.ByName("dir")) + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if path == root { + return nil + } + + list = append(list, filepath.Base(path)) + + return nil + }) + + if err != nil { + if os.IsNotExist(err) { + c.Fail(404, err) + } else { + c.Fail(500, err) + } + return + } + + c.JSON(200, list) +} + +// DELETE /files/:dir +func apiFilesDeleteDir(c *gin.Context) { + if !verifyDir(c) { + return + } + + err := os.RemoveAll(filepath.Join(context.UploadPath(), c.Params.ByName("dir"))) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, gin.H{}) +} + +// DELETE /files/:dir/:name +func apiFilesDeleteFile(c *gin.Context) { + if !verifyDir(c) { + return + } + + if !verifyPath(c.Params.ByName("name")) { + c.Fail(400, fmt.Errorf("wrong file")) + return + } + + err := os.Remove(filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("name"))) + if err != nil { + if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) { + c.Fail(500, err) + return + } + } + + c.JSON(200, gin.H{}) +} diff --git a/src/github.com/smira/aptly/api/graph.go b/src/github.com/smira/aptly/api/graph.go new file mode 100644 index 00000000..66ed4444 --- /dev/null +++ b/src/github.com/smira/aptly/api/graph.go @@ -0,0 +1,75 @@ +package api + +import ( + "bytes" + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/deb" + "io" + "mime" + "os" + "os/exec" +) + +// GET /api/graph.:ext +func apiGraph(c *gin.Context) { + var ( + err error + output []byte + ) + + ext := c.Params.ByName("ext") + + factory := context.CollectionFactory() + + factory.RemoteRepoCollection().RLock() + defer factory.RemoteRepoCollection().RUnlock() + factory.LocalRepoCollection().RLock() + defer factory.LocalRepoCollection().RUnlock() + factory.SnapshotCollection().RLock() + defer factory.SnapshotCollection().RUnlock() + factory.PublishedRepoCollection().RLock() + defer factory.PublishedRepoCollection().RUnlock() + + graph, err := deb.BuildGraph(factory) + if err != nil { + c.JSON(500, err) + return + } + + buf := bytes.NewBufferString(graph.String()) + + command := exec.Command("dot", "-T"+ext) + command.Stderr = os.Stderr + + stdin, err := command.StdinPipe() + if err != nil { + c.Fail(500, err) + return + } + + _, err = io.Copy(stdin, buf) + if err != nil { + c.Fail(500, err) + return + } + + err = stdin.Close() + if err != nil { + c.Fail(500, err) + return + } + + output, err = command.Output() + if err != nil { + c.Fail(500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err)) + return + } + + mimeType := mime.TypeByExtension("." + ext) + if mimeType == "" { + mimeType = "application/octet-stream" + } + + c.Data(200, mimeType, output) +} diff --git a/src/github.com/smira/aptly/api/packages.go b/src/github.com/smira/aptly/api/packages.go new file mode 100644 index 00000000..5a9edaed --- /dev/null +++ b/src/github.com/smira/aptly/api/packages.go @@ -0,0 +1,16 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// GET /api/packages/:key +func apiPackagesShow(c *gin.Context) { + p, err := context.CollectionFactory().PackageCollection().ByKey([]byte(c.Params.ByName("key"))) + if err != nil { + c.Fail(404, err) + return + } + + c.JSON(200, p) +} diff --git a/src/github.com/smira/aptly/api/publish.go b/src/github.com/smira/aptly/api/publish.go new file mode 100644 index 00000000..a38b80c2 --- /dev/null +++ b/src/github.com/smira/aptly/api/publish.go @@ -0,0 +1,336 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/utils" + "strings" +) + +// SigningOptions is a shared between publish API GPG options structure +type SigningOptions struct { + Skip bool + Batch bool + GpgKey string + Keyring string + SecretKeyring string + Passphrase string + PassphraseFile string +} + +func getSigner(options *SigningOptions) (utils.Signer, error) { + if options.Skip { + return nil, nil + } + + signer := &utils.GpgSigner{} + signer.SetKey(options.GpgKey) + signer.SetKeyRing(options.Keyring, options.SecretKeyring) + signer.SetPassphrase(options.Passphrase, options.PassphraseFile) + signer.SetBatch(options.Batch) + + err := signer.Init() + if err != nil { + return nil, err + } + + return signer, nil +} + +// Replace '_' with '/' and double '__' with single '_' +func parseEscapedPath(path string) string { + result := strings.Replace(strings.Replace(path, "_", "/", -1), "//", "_", -1) + if result == "" { + result = "." + } + return result +} + +// GET /publish +func apiPublishList(c *gin.Context) { + localCollection := context.CollectionFactory().LocalRepoCollection() + localCollection.RLock() + defer localCollection.RUnlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.RLock() + defer snapshotCollection.RUnlock() + + collection := context.CollectionFactory().PublishedRepoCollection() + collection.RLock() + defer collection.RUnlock() + + result := make([]*deb.PublishedRepo, 0, collection.Len()) + + err := collection.ForEach(func(repo *deb.PublishedRepo) error { + err := collection.LoadComplete(repo, context.CollectionFactory()) + if err != nil { + return err + } + + result = append(result, repo) + + return nil + }) + + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, result) +} + +// POST /publish/:prefix +func apiPublishRepoOrSnapshot(c *gin.Context) { + param := parseEscapedPath(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + + var b struct { + SourceKind string `binding:"required"` + Sources []struct { + Component string + Name string `binding:"required"` + } `binding:"required"` + Distribution string + Label string + Origin string + ForceOverwrite bool + Architectures []string + Signing SigningOptions + } + + if !c.Bind(&b) { + return + } + + signer, err := getSigner(&b.Signing) + if err != nil { + c.Fail(500, fmt.Errorf("unable to initialize GPG signer: %s", err)) + return + } + + if len(b.Sources) == 0 { + c.Fail(400, fmt.Errorf("unable to publish: soures are empty")) + return + } + + var components []string + var sources []interface{} + + if b.SourceKind == "snapshot" { + var snapshot *deb.Snapshot + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.RLock() + defer snapshotCollection.RUnlock() + + for _, source := range b.Sources { + components = append(components, source.Component) + + snapshot, err = snapshotCollection.ByName(source.Name) + if err != nil { + c.Fail(404, fmt.Errorf("unable to publish: %s", err)) + return + } + + err = snapshotCollection.LoadComplete(snapshot) + if err != nil { + c.Fail(500, fmt.Errorf("unable to publish: %s", err)) + return + } + + sources = append(sources, snapshot) + } + } else if b.SourceKind == "local" { + var localRepo *deb.LocalRepo + + localCollection := context.CollectionFactory().LocalRepoCollection() + localCollection.RLock() + defer localCollection.RUnlock() + + for _, source := range b.Sources { + components = append(components, source.Component) + + localRepo, err = localCollection.ByName(source.Name) + if err != nil { + c.Fail(404, fmt.Errorf("unable to publish: %s", err)) + return + } + + err = localCollection.LoadComplete(localRepo) + if err != nil { + c.Fail(500, fmt.Errorf("unable to publish: %s", err)) + } + + sources = append(sources, localRepo) + } + } else { + c.Fail(400, fmt.Errorf("unknown SourceKind")) + return + } + + collection := context.CollectionFactory().PublishedRepoCollection() + collection.Lock() + defer collection.Unlock() + + published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, context.CollectionFactory()) + if err != nil { + c.Fail(500, fmt.Errorf("unable to publish: %s", err)) + return + } + published.Origin = b.Origin + published.Label = b.Label + + duplicate := collection.CheckDuplicate(published) + if duplicate != nil { + context.CollectionFactory().PublishedRepoCollection().LoadComplete(duplicate, context.CollectionFactory()) + c.Fail(400, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)) + return + } + + err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite) + if err != nil { + c.Fail(500, fmt.Errorf("unable to publish: %s", err)) + return + } + + err = collection.Add(published) + if err != nil { + c.Fail(500, fmt.Errorf("unable to save to DB: %s", err)) + } + + c.JSON(201, published) +} + +// PUT /publish/:prefix/:distribution +func apiPublishUpdateSwitch(c *gin.Context) { + param := parseEscapedPath(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := c.Params.ByName("distribution") + + var b struct { + ForceOverwrite bool + Signing SigningOptions + Snapshots []struct { + Component string `binding:"required"` + Name string `binding:"required"` + } + } + + if !c.Bind(&b) { + return + } + + signer, err := getSigner(&b.Signing) + if err != nil { + c.Fail(500, fmt.Errorf("unable to initialize GPG signer: %s", err)) + return + } + + // published.LoadComplete would touch local repo collection + localRepoCollection := context.CollectionFactory().LocalRepoCollection() + localRepoCollection.RLock() + defer localRepoCollection.RUnlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.RLock() + defer snapshotCollection.RUnlock() + + collection := context.CollectionFactory().PublishedRepoCollection() + collection.Lock() + defer collection.Unlock() + + published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution) + if err != nil { + c.Fail(404, fmt.Errorf("unable to update: %s", err)) + return + } + err = collection.LoadComplete(published, context.CollectionFactory()) + if err != nil { + c.Fail(500, fmt.Errorf("unable to update: %s", err)) + return + } + + var updatedComponents []string + + if published.SourceKind == "local" { + if len(b.Snapshots) > 0 { + c.Fail(400, fmt.Errorf("snapshots shouldn't be given when updating local repo")) + return + } + updatedComponents = published.Components() + for _, component := range updatedComponents { + published.UpdateLocalRepo(component) + } + } else if published.SourceKind == "snapshot" { + publishedComponents := published.Components() + for _, snapshotInfo := range b.Snapshots { + if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) { + c.Fail(404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component)) + return + } + + snapshot, err := snapshotCollection.ByName(snapshotInfo.Name) + if err != nil { + c.Fail(404, err) + return + } + + err = snapshotCollection.LoadComplete(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + published.UpdateSnapshot(snapshotInfo.Component, snapshot) + updatedComponents = append(updatedComponents, snapshotInfo.Component) + } + } else { + c.Fail(500, fmt.Errorf("unknown published repository type")) + } + + err = published.Publish(context.PackagePool(), context, context.CollectionFactory(), signer, nil, b.ForceOverwrite) + if err != nil { + c.Fail(500, fmt.Errorf("unable to update: %s", err)) + } + + err = collection.Update(published) + if err != nil { + c.Fail(500, fmt.Errorf("unable to save to DB: %s", err)) + } + + err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents, + context.GetPublishedStorage(storage), context.CollectionFactory(), nil) + if err != nil { + c.Fail(500, fmt.Errorf("unable to update: %s", err)) + } + + c.JSON(200, published) +} + +// DELETE /publish/:prefix/:distribution +func apiPublishDrop(c *gin.Context) { + param := parseEscapedPath(c.Params.ByName("prefix")) + storage, prefix := deb.ParsePrefix(param) + distribution := c.Params.ByName("distribution") + + // published.LoadComplete would touch local repo collection + localRepoCollection := context.CollectionFactory().LocalRepoCollection() + localRepoCollection.RLock() + defer localRepoCollection.RUnlock() + + collection := context.CollectionFactory().PublishedRepoCollection() + collection.Lock() + defer collection.Unlock() + + err := collection.Remove(context, storage, prefix, distribution, + context.CollectionFactory(), context.Progress()) + if err != nil { + c.Fail(500, fmt.Errorf("unable to drop: %s", err)) + return + } + + c.JSON(200, gin.H{}) +} diff --git a/src/github.com/smira/aptly/api/repos.go b/src/github.com/smira/aptly/api/repos.go new file mode 100644 index 00000000..17e5b550 --- /dev/null +++ b/src/github.com/smira/aptly/api/repos.go @@ -0,0 +1,370 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/database" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/utils" + "os" + "path/filepath" +) + +// GET /api/repos +func apiReposList(c *gin.Context) { + result := []*deb.LocalRepo{} + + collection := context.CollectionFactory().LocalRepoCollection() + collection.RLock() + defer collection.RUnlock() + + context.CollectionFactory().LocalRepoCollection().ForEach(func(r *deb.LocalRepo) error { + result = append(result, r) + return nil + }) + + c.JSON(200, result) +} + +// POST /api/repos +func apiReposCreate(c *gin.Context) { + var b struct { + Name string `binding:"required"` + Comment string + DefaultDistribution string + DefaultComponent string + } + + if !c.Bind(&b) { + return + } + + repo := deb.NewLocalRepo(b.Name, b.Comment) + repo.DefaultComponent = b.DefaultComponent + repo.DefaultDistribution = b.DefaultDistribution + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + err := context.CollectionFactory().LocalRepoCollection().Add(repo) + if err != nil { + c.Fail(400, err) + return + } + + c.JSON(201, repo) +} + +// PUT /api/repos/:name +func apiReposEdit(c *gin.Context) { + var b struct { + Comment string + DefaultDistribution string + DefaultComponent string + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + if b.Comment != "" { + repo.Comment = b.Comment + } + if b.DefaultDistribution != "" { + repo.DefaultDistribution = b.DefaultDistribution + } + if b.DefaultComponent != "" { + repo.DefaultComponent = b.DefaultComponent + } + + err = collection.Update(repo) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, repo) +} + +// GET /api/repos/:name +func apiReposShow(c *gin.Context) { + collection := context.CollectionFactory().LocalRepoCollection() + collection.RLock() + defer collection.RUnlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + c.JSON(200, repo) +} + +// DELETE /api/repos/:name +func apiReposDrop(c *gin.Context) { + force := c.Request.URL.Query().Get("force") == "1" + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.RLock() + defer snapshotCollection.RUnlock() + + publishedCollection := context.CollectionFactory().PublishedRepoCollection() + publishedCollection.RLock() + defer publishedCollection.RUnlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + published := publishedCollection.ByLocalRepo(repo) + if len(published) > 0 { + c.Fail(409, fmt.Errorf("unable to drop, local repo is published")) + return + } + + if !force { + snapshots := snapshotCollection.ByLocalRepoSource(repo) + if len(snapshots) > 0 { + c.Fail(409, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override")) + return + } + } + + err = collection.Drop(repo) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, gin.H{}) +} + +// GET /api/repos/:name/packages +func apiReposPackagesShow(c *gin.Context) { + collection := context.CollectionFactory().LocalRepoCollection() + collection.RLock() + defer collection.RUnlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + showPackages(c, repo.RefList()) +} + +// Handler for both add and delete +func apiReposPackagesAddDelete(c *gin.Context, cb func(list *deb.PackageList, p *deb.Package) error) { + var b struct { + PackageRefs []string + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + list, err := deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), nil) + if err != nil { + c.Fail(500, err) + return + } + + // verify package refs and build package list + for _, ref := range b.PackageRefs { + var p *deb.Package + + p, err = context.CollectionFactory().PackageCollection().ByKey([]byte(ref)) + if err != nil { + if err == database.ErrNotFound { + c.Fail(404, fmt.Errorf("package %s: %s", ref, err)) + } else { + c.Fail(500, err) + } + return + } + err = cb(list, p) + if err != nil { + c.Fail(400, err) + return + } + } + + repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) + + err = context.CollectionFactory().LocalRepoCollection().Update(repo) + if err != nil { + c.Fail(500, fmt.Errorf("unable to save: %s", err)) + return + } + + c.JSON(200, repo) + +} + +// POST /repos/:name/packages +func apiReposPackagesAdd(c *gin.Context) { + apiReposPackagesAddDelete(c, func(list *deb.PackageList, p *deb.Package) error { + return list.Add(p) + }) +} + +// DELETE /repos/:name/packages +func apiReposPackagesDelete(c *gin.Context) { + apiReposPackagesAddDelete(c, func(list *deb.PackageList, p *deb.Package) error { + list.Remove(p) + return nil + }) +} + +// POST /repos/:name/file/:dir/:file +func apiReposPackageFromFile(c *gin.Context) { + // redirect all work to dir method + apiReposPackageFromDir(c) +} + +// POST /repos/:name/file/:dir +func apiReposPackageFromDir(c *gin.Context) { + forceReplace := c.Request.URL.Query().Get("forceReplace") == "1" + noRemove := c.Request.URL.Query().Get("noRemove") == "1" + + if !verifyDir(c) { + return + } + + fileParam := c.Params.ByName("file") + if fileParam != "" && !verifyPath(fileParam) { + c.Fail(400, fmt.Errorf("wrong file")) + return + } + + collection := context.CollectionFactory().LocalRepoCollection() + collection.Lock() + defer collection.Unlock() + + repo, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + verifier := &utils.GpgVerifier{} + + var ( + sources []string + packageFiles, failedFiles []string + processedFiles, failedFiles2 []string + reporter = &aptly.RecordingResultReporter{ + Warnings: []string{}, + AddedLines: []string{}, + RemovedLines: []string{}, + } + list *deb.PackageList + ) + + if fileParam == "" { + sources = []string{filepath.Join(context.UploadPath(), c.Params.ByName("dir"))} + } else { + sources = []string{filepath.Join(context.UploadPath(), c.Params.ByName("dir"), c.Params.ByName("file"))} + } + + packageFiles, failedFiles, err = deb.CollectPackageFiles(sources, reporter) + + if err != nil { + c.Fail(500, fmt.Errorf("unable to collect package files: %s", err)) + return + } + + list, err = deb.NewPackageListFromRefList(repo.RefList(), context.CollectionFactory().PackageCollection(), nil) + if err != nil { + c.Fail(500, fmt.Errorf("unable to load packages: %s", err)) + return + } + + processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(), + context.CollectionFactory().PackageCollection(), reporter) + failedFiles = append(failedFiles, failedFiles2...) + + if err != nil { + c.Fail(500, fmt.Errorf("unable to import package files: %s", err)) + return + } + + repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) + + err = context.CollectionFactory().LocalRepoCollection().Update(repo) + if err != nil { + c.Fail(500, fmt.Errorf("unable to save: %s", err)) + return + } + + if !noRemove { + processedFiles = utils.StrSliceDeduplicate(processedFiles) + + for _, file := range processedFiles { + err := os.Remove(file) + if err != nil { + reporter.Warning("unable to remove file %s: %s", file, err) + } + } + + // atempt to remove dir, if it fails, that's fine: probably it's not empty + os.Remove(filepath.Join(context.UploadPath(), c.Params.ByName("dir"))) + } + + if failedFiles == nil { + failedFiles = []string{} + } + + c.JSON(200, gin.H{ + "Report": reporter, + "FailedFiles": failedFiles, + }) +} diff --git a/src/github.com/smira/aptly/api/router.go b/src/github.com/smira/aptly/api/router.go new file mode 100644 index 00000000..8dcd8c6e --- /dev/null +++ b/src/github.com/smira/aptly/api/router.go @@ -0,0 +1,82 @@ +package api + +import ( + "github.com/gin-gonic/gin" + ctx "github.com/smira/aptly/context" + "net/http" +) + +var context *ctx.AptlyContext + +// Router returns prebuilt with routes http.Handler +func Router(c *ctx.AptlyContext) http.Handler { + context = c + + go cacheFlusher() + + router := gin.Default() + router.Use(gin.ErrorLogger()) + + root := router.Group("/api") + + { + root.GET("/version", apiVersion) + } + + { + root.GET("/repos", apiReposList) + root.POST("/repos", apiReposCreate) + root.GET("/repos/:name", apiReposShow) + root.PUT("/repos/:name", apiReposEdit) + root.DELETE("/repos/:name", apiReposDrop) + + root.GET("/repos/:name/packages", apiReposPackagesShow) + root.POST("/repos/:name/packages", apiReposPackagesAdd) + root.DELETE("/repos/:name/packages", apiReposPackagesDelete) + + root.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile) + root.POST("/repos/:name/file/:dir", apiReposPackageFromDir) + + root.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository) + } + + { + root.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror) + } + + { + root.GET("/files", apiFilesListDirs) + root.POST("/files/:dir", apiFilesUpload) + root.GET("/files/:dir", apiFilesListFiles) + root.DELETE("/files/:dir", apiFilesDeleteDir) + root.DELETE("/files/:dir/:name", apiFilesDeleteFile) + } + + { + root.GET("/publish", apiPublishList) + root.POST("/publish", apiPublishRepoOrSnapshot) + root.POST("/publish/:prefix", apiPublishRepoOrSnapshot) + root.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch) + root.DELETE("/publish/:prefix/:distribution", apiPublishDrop) + } + + { + root.GET("/snapshots", apiSnapshotsList) + root.POST("/snapshots", apiSnapshotsCreate) + root.PUT("/snapshots/:name", apiSnapshotsUpdate) + root.GET("/snapshots/:name", apiSnapshotsShow) + root.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages) + root.DELETE("/snapshots/:name", apiSnapshotsDrop) + root.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff) + } + + { + root.GET("/packages/:key", apiPackagesShow) + } + + { + root.GET("/graph.:ext", apiGraph) + } + + return router +} diff --git a/src/github.com/smira/aptly/api/snapshot.go b/src/github.com/smira/aptly/api/snapshot.go new file mode 100644 index 00000000..c4f475a0 --- /dev/null +++ b/src/github.com/smira/aptly/api/snapshot.go @@ -0,0 +1,410 @@ +package api + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/smira/aptly/database" + "github.com/smira/aptly/deb" +) + +// GET /api/snapshots +func apiSnapshotsList(c *gin.Context) { + SortMethodString := c.Request.URL.Query().Get("sort") + + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + if SortMethodString == "" { + SortMethodString = "name" + } + + result := []*deb.Snapshot{} + collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error { + result = append(result, snapshot) + return nil + }) + + c.JSON(200, result) +} + +// POST /api/mirrors/:name/snapshots/ +func apiSnapshotsCreateFromMirror(c *gin.Context) { + var ( + err error + repo *deb.RemoteRepo + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + Description string + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().RemoteRepoCollection() + collection.RLock() + defer collection.RUnlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + repo, err = collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = repo.CheckLock() + if err != nil { + c.Fail(409, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + snapshot, err = deb.NewSnapshotFromRepository(b.Name, repo) + if err != nil { + c.Fail(400, err) + return + } + + if b.Description != "" { + snapshot.Description = b.Description + } + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(400, err) + return + } + + c.JSON(201, snapshot) +} + +// POST /api/snapshots +func apiSnapshotsCreate(c *gin.Context) { + var ( + err error + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + Description string + SourceSnapshots []string + PackageRefs []string + } + + if !c.Bind(&b) { + return + } + + if b.Description == "" { + if len(b.SourceSnapshots)+len(b.PackageRefs) == 0 { + b.Description = "Created as empty" + } + } + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + sources := make([]*deb.Snapshot, len(b.SourceSnapshots)) + + for i := range b.SourceSnapshots { + sources[i], err = snapshotCollection.ByName(b.SourceSnapshots[i]) + if err != nil { + c.Fail(404, err) + return + } + + err = snapshotCollection.LoadComplete(sources[i]) + if err != nil { + c.Fail(500, err) + return + } + } + + list := deb.NewPackageList() + + // verify package refs and build package list + for _, ref := range b.PackageRefs { + var p *deb.Package + + p, err = context.CollectionFactory().PackageCollection().ByKey([]byte(ref)) + if err != nil { + if err == database.ErrNotFound { + c.Fail(404, fmt.Errorf("package %s: %s", ref, err)) + } else { + c.Fail(500, err) + } + return + } + err = list.Add(p) + if err != nil { + c.Fail(400, err) + return + } + } + + snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description) + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(400, err) + return + } + + c.JSON(201, snapshot) +} + +// POST /api/repos/:name/snapshots +func apiSnapshotsCreateFromRepository(c *gin.Context) { + var ( + err error + repo *deb.LocalRepo + snapshot *deb.Snapshot + ) + + var b struct { + Name string `binding:"required"` + Description string + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().LocalRepoCollection() + collection.RLock() + defer collection.RUnlock() + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + repo, err = collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(repo) + if err != nil { + c.Fail(500, err) + return + } + + snapshot, err = deb.NewSnapshotFromLocalRepo(b.Name, repo) + if err != nil { + c.Fail(400, err) + return + } + + if b.Description != "" { + snapshot.Description = b.Description + } + + err = snapshotCollection.Add(snapshot) + if err != nil { + c.Fail(400, err) + return + } + + c.JSON(201, snapshot) +} + +// PUT /api/snapshots/:name +func apiSnapshotsUpdate(c *gin.Context) { + var ( + err error + snapshot *deb.Snapshot + ) + + var b struct { + Name string + Description string + } + + if !c.Bind(&b) { + return + } + + collection := context.CollectionFactory().SnapshotCollection() + collection.Lock() + defer collection.Unlock() + + snapshot, err = collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + _, err = collection.ByName(b.Name) + if err == nil { + c.Fail(409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)) + return + } + + if b.Name != "" { + snapshot.Name = b.Name + } + + if b.Description != "" { + snapshot.Description = b.Description + } + + err = context.CollectionFactory().SnapshotCollection().Update(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, snapshot) +} + +// GET /api/snapshots/:name +func apiSnapshotsShow(c *gin.Context) { + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + snapshot, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, snapshot) +} + +// DELETE /api/snapshots/:name +func apiSnapshotsDrop(c *gin.Context) { + name := c.Params.ByName("name") + force := c.Request.URL.Query().Get("force") == "1" + + snapshotCollection := context.CollectionFactory().SnapshotCollection() + snapshotCollection.Lock() + defer snapshotCollection.Unlock() + + publishedCollection := context.CollectionFactory().PublishedRepoCollection() + publishedCollection.RLock() + defer publishedCollection.RUnlock() + + snapshot, err := snapshotCollection.ByName(name) + if err != nil { + c.Fail(404, err) + return + } + + published := publishedCollection.BySnapshot(snapshot) + + if len(published) > 0 { + c.Fail(409, fmt.Errorf("unable to drop: snapshot is published")) + return + } + + if !force { + snapshots := snapshotCollection.BySnapshotSource(snapshot) + if len(snapshots) > 0 { + c.Fail(409, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")) + return + } + } + + err = snapshotCollection.Drop(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + c.JSON(200, gin.H{}) +} + +// GET /api/snapshots/:name/diff/:withSnapshot +func apiSnapshotsDiff(c *gin.Context) { + onlyMatching := c.Request.URL.Query().Get("onlyMatching") == "1" + + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + snapshotA, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + snapshotB, err := collection.ByName(c.Params.ByName("withSnapshot")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(snapshotA) + if err != nil { + c.Fail(500, err) + return + } + + err = collection.LoadComplete(snapshotB) + if err != nil { + c.Fail(500, err) + return + } + + // Calculate diff + diff, err := snapshotA.RefList().Diff(snapshotB.RefList(), context.CollectionFactory().PackageCollection()) + if err != nil { + c.Fail(500, err) + return + } + + result := []deb.PackageDiff{} + + for _, pdiff := range diff { + if onlyMatching && (pdiff.Left == nil || pdiff.Right == nil) { + continue + } + + result = append(result, pdiff) + } + + c.JSON(200, result) +} + +// GET /api/snapshots/:name/packages +func apiSnapshotsSearchPackages(c *gin.Context) { + collection := context.CollectionFactory().SnapshotCollection() + collection.RLock() + defer collection.RUnlock() + + snapshot, err := collection.ByName(c.Params.ByName("name")) + if err != nil { + c.Fail(404, err) + return + } + + err = collection.LoadComplete(snapshot) + if err != nil { + c.Fail(500, err) + return + } + + showPackages(c, snapshot.RefList()) +} diff --git a/src/github.com/smira/aptly/aptly/report.go b/src/github.com/smira/aptly/aptly/report.go new file mode 100644 index 00000000..eed88d48 --- /dev/null +++ b/src/github.com/smira/aptly/aptly/report.go @@ -0,0 +1,67 @@ +package aptly + +import ( + "fmt" +) + +// ResultReporter is abstraction for result reporting from complex processing functions +type ResultReporter interface { + // Warning is non-fatal error message + Warning(msg string, a ...interface{}) + // Removed is signal that something has been removed + Removed(msg string, a ...interface{}) + // Added is signal that something has been added + Added(msg string, a ...interface{}) +} + +// ConsoleResultReporter is implementation of ResultReporter that prints in colors to console +type ConsoleResultReporter struct { + Progress Progress +} + +// Check interface +var ( + _ ResultReporter = &ConsoleResultReporter{} +) + +// Warning is non-fatal error message (yellow) +func (c *ConsoleResultReporter) Warning(msg string, a ...interface{}) { + c.Progress.ColoredPrintf("@y[!]@| @!"+msg+"@|", a...) +} + +// Removed is signal that something has been removed (red) +func (c *ConsoleResultReporter) Removed(msg string, a ...interface{}) { + c.Progress.ColoredPrintf("@r[-]@| "+msg, a...) +} + +// Added is signal that something has been added (green) +func (c *ConsoleResultReporter) Added(msg string, a ...interface{}) { + c.Progress.ColoredPrintf("@g[+]@| "+msg, a...) +} + +// RecordingResultReporter is implementation of ResultReporter that collects all messages +type RecordingResultReporter struct { + Warnings []string + AddedLines []string `json:"Added"` + RemovedLines []string `json:"Removed"` +} + +// Check interface +var ( + _ ResultReporter = &RecordingResultReporter{} +) + +// Warning is non-fatal error message +func (r *RecordingResultReporter) Warning(msg string, a ...interface{}) { + r.Warnings = append(r.Warnings, fmt.Sprintf(msg, a...)) +} + +// Removed is signal that something has been removed +func (r *RecordingResultReporter) Removed(msg string, a ...interface{}) { + r.RemovedLines = append(r.RemovedLines, fmt.Sprintf(msg, a...)) +} + +// Added is signal that something has been added +func (r *RecordingResultReporter) Added(msg string, a ...interface{}) { + r.AddedLines = append(r.AddedLines, fmt.Sprintf(msg, a...)) +} diff --git a/src/github.com/smira/aptly/aptly/version.go b/src/github.com/smira/aptly/aptly/version.go index f28e1044..d6e947dc 100644 --- a/src/github.com/smira/aptly/aptly/version.go +++ b/src/github.com/smira/aptly/aptly/version.go @@ -1,7 +1,7 @@ package aptly // Version of aptly -const Version = "0.8" +const Version = "0.9.1" // Enable debugging features? const EnableDebug = false diff --git a/src/github.com/smira/aptly/cmd/api.go b/src/github.com/smira/aptly/cmd/api.go new file mode 100644 index 00000000..3057b840 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/api.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/smira/commander" +) + +func makeCmdAPI() *commander.Command { + return &commander.Command{ + UsageLine: "api", + Short: "start API server/issue requests", + Subcommands: []*commander.Command{ + makeCmdAPIServe(), + }, + } +} diff --git a/src/github.com/smira/aptly/cmd/api_serve.go b/src/github.com/smira/aptly/cmd/api_serve.go new file mode 100644 index 00000000..6d62322e --- /dev/null +++ b/src/github.com/smira/aptly/cmd/api_serve.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "fmt" + "github.com/smira/aptly/api" + "github.com/smira/commander" + "github.com/smira/flag" + "net/http" +) + +func aptlyAPIServe(cmd *commander.Command, args []string) error { + var ( + err error + ) + + if len(args) != 0 { + cmd.Usage() + return commander.ErrCommandError + } + + listen := context.Flags().Lookup("listen").Value.String() + + fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen) + + err = http.ListenAndServe(listen, api.Router(context)) + if err != nil { + return fmt.Errorf("unable to serve: %s", err) + } + + return err +} + +func makeCmdAPIServe() *commander.Command { + cmd := &commander.Command{ + Run: aptlyAPIServe, + UsageLine: "serve", + Short: "start API HTTP service", + Long: ` +Stat HTTP server with aptly REST API. + +Example: + + $ aptly api serve -listen=:8080 +`, + Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError), + } + + cmd.Flag.String("listen", ":8080", "host:port for HTTP listening") + + return cmd + +} diff --git a/src/github.com/smira/aptly/cmd/cmd.go b/src/github.com/smira/aptly/cmd/cmd.go index 0b635297..4bec1f74 100644 --- a/src/github.com/smira/aptly/cmd/cmd.go +++ b/src/github.com/smira/aptly/cmd/cmd.go @@ -65,17 +65,18 @@ fine-grained changes in repository contents to transition your package environment to new version.`, Flag: *flag.NewFlagSet("aptly", flag.ExitOnError), Subcommands: []*commander.Command{ + makeCmdConfig(), makeCmdDb(), makeCmdGraph(), makeCmdMirror(), makeCmdRepo(), makeCmdServe(), makeCmdSnapshot(), - // Disabled on no docs - //makeCmdTask(), + makeCmdTask(), makeCmdPublish(), makeCmdVersion(), makeCmdPackage(), + makeCmdAPI(), }, } diff --git a/src/github.com/smira/aptly/cmd/config.go b/src/github.com/smira/aptly/cmd/config.go new file mode 100644 index 00000000..5eb27307 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/config.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/smira/commander" +) + +func makeCmdConfig() *commander.Command { + return &commander.Command{ + UsageLine: "config", + Short: "manage aptly configuration", + Subcommands: []*commander.Command{ + makeCmdConfigShow(), + }, + } +} diff --git a/src/github.com/smira/aptly/cmd/config_show.go b/src/github.com/smira/aptly/cmd/config_show.go new file mode 100644 index 00000000..04f28648 --- /dev/null +++ b/src/github.com/smira/aptly/cmd/config_show.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/smira/commander" +) + +func aptlyConfigShow(cmd *commander.Command, args []string) error { + + config := context.Config() + prettyJSON, err := json.MarshalIndent(config, "", " ") + + if err != nil { + return fmt.Errorf("unable to dump the config file: %s", err) + } + + fmt.Println(string(prettyJSON)) + + return nil +} + +func makeCmdConfigShow() *commander.Command { + cmd := &commander.Command{ + Run: aptlyConfigShow, + UsageLine: "show", + Short: "show current aptly's config", + Long: ` +Command show displays the current aptly configuration. + +Example: + + $ aptly config show + +`, + } + return cmd +} diff --git a/src/github.com/smira/aptly/cmd/context.go b/src/github.com/smira/aptly/cmd/context.go index f8472f4f..882642a1 100644 --- a/src/github.com/smira/aptly/cmd/context.go +++ b/src/github.com/smira/aptly/cmd/context.go @@ -1,313 +1,20 @@ package cmd import ( - "fmt" - "github.com/smira/aptly/aptly" - "github.com/smira/aptly/console" - "github.com/smira/aptly/database" - "github.com/smira/aptly/deb" - "github.com/smira/aptly/files" - "github.com/smira/aptly/http" - "github.com/smira/aptly/s3" - "github.com/smira/aptly/utils" - "github.com/smira/commander" + ctx "github.com/smira/aptly/context" "github.com/smira/flag" - "os" - "path/filepath" - "runtime" - "runtime/pprof" - "strings" - "time" ) -// AptlyContext is a common context shared by all commands -type AptlyContext struct { - flags, globalFlags *flag.FlagSet - configLoaded bool - - progress aptly.Progress - downloader aptly.Downloader - database database.Storage - packagePool aptly.PackagePool - publishedStorages map[string]aptly.PublishedStorage - collectionFactory *deb.CollectionFactory - dependencyOptions int - architecturesList []string - // Debug features - fileCPUProfile *os.File - fileMemProfile *os.File - fileMemStats *os.File -} - -var context *AptlyContext - -// Check interface -var _ aptly.PublishedStorageProvider = &AptlyContext{} - -// FatalError is type for panicking to abort execution with non-zero -// exit code and print meaningful explanation -type FatalError struct { - ReturnCode int - Message string -} - -// Fatal panics and aborts execution with exit code 1 -func Fatal(err error) { - returnCode := 1 - if err == commander.ErrFlagError || err == commander.ErrCommandError { - returnCode = 2 - } - panic(&FatalError{ReturnCode: returnCode, Message: err.Error()}) -} - -// Config loads and returns current configuration -func (context *AptlyContext) Config() *utils.ConfigStructure { - if !context.configLoaded { - var err error - - configLocation := context.globalFlags.Lookup("config").Value.String() - if configLocation != "" { - err = utils.LoadConfig(configLocation, &utils.Config) - - if err != nil { - Fatal(err) - } - } else { - configLocations := []string{ - filepath.Join(os.Getenv("HOME"), ".aptly.conf"), - "/etc/aptly.conf", - } - - for _, configLocation := range configLocations { - err = utils.LoadConfig(configLocation, &utils.Config) - if err == nil { - break - } - if !os.IsNotExist(err) { - Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err)) - } - } - - if err != nil { - fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0]) - utils.SaveConfig(configLocations[0], &utils.Config) - } - } - - context.configLoaded = true - - } - return &utils.Config -} - -// DependencyOptions calculates options related to dependecy handling -func (context *AptlyContext) DependencyOptions() int { - if context.dependencyOptions == -1 { - context.dependencyOptions = 0 - if LookupOption(context.Config().DepFollowSuggests, context.globalFlags, "dep-follow-suggests") { - context.dependencyOptions |= deb.DepFollowSuggests - } - if LookupOption(context.Config().DepFollowRecommends, context.globalFlags, "dep-follow-recommends") { - context.dependencyOptions |= deb.DepFollowRecommends - } - if LookupOption(context.Config().DepFollowAllVariants, context.globalFlags, "dep-follow-all-variants") { - context.dependencyOptions |= deb.DepFollowAllVariants - } - if LookupOption(context.Config().DepFollowSource, context.globalFlags, "dep-follow-source") { - context.dependencyOptions |= deb.DepFollowSource - } - } - - return context.dependencyOptions -} - -// ArchitecturesList returns list of architectures fixed via command line or config -func (context *AptlyContext) ArchitecturesList() []string { - if context.architecturesList == nil { - context.architecturesList = context.Config().Architectures - optionArchitectures := context.globalFlags.Lookup("architectures").Value.String() - if optionArchitectures != "" { - context.architecturesList = strings.Split(optionArchitectures, ",") - } - } - - return context.architecturesList -} - -// Progress creates or returns Progress object -func (context *AptlyContext) Progress() aptly.Progress { - if context.progress == nil { - context.progress = console.NewProgress() - context.progress.Start() - } - - return context.progress -} - -// Downloader returns instance of current downloader -func (context *AptlyContext) Downloader() aptly.Downloader { - if context.downloader == nil { - var downloadLimit int64 - limitFlag := context.flags.Lookup("download-limit") - if limitFlag != nil { - downloadLimit = limitFlag.Value.Get().(int64) - } - if downloadLimit == 0 { - downloadLimit = context.Config().DownloadLimit - } - context.downloader = http.NewDownloader(context.Config().DownloadConcurrency, - downloadLimit*1024, context.Progress()) - } - - return context.downloader -} - -// DBPath builds path to database -func (context *AptlyContext) DBPath() string { - return filepath.Join(context.Config().RootDir, "db") -} - -// Database opens and returns current instance of database -func (context *AptlyContext) Database() (database.Storage, error) { - if context.database == nil { - var err error - - context.database, err = database.OpenDB(context.DBPath()) - if err != nil { - return nil, fmt.Errorf("can't open database: %s", err) - } - } - - return context.database, nil -} - -// CloseDatabase closes the db temporarily -func (context *AptlyContext) CloseDatabase() error { - if context.database == nil { - return nil - } - - return context.database.Close() -} - -// ReOpenDatabase reopens the db after close -func (context *AptlyContext) ReOpenDatabase() error { - if context.database == nil { - return nil - } - - const MaxTries = 10 - const Delay = 10 * time.Second - - for try := 0; try < MaxTries; try++ { - err := context.database.ReOpen() - if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 { - return err - } - context.Progress().Printf("Unable to reopen database, sleeping %s\n", Delay) - <-time.After(Delay) - } - - return fmt.Errorf("unable to reopen the DB, maximum number of retries reached") -} - -// CollectionFactory builds factory producing all kinds of collections -func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory { - if context.collectionFactory == nil { - db, err := context.Database() - if err != nil { - Fatal(err) - } - context.collectionFactory = deb.NewCollectionFactory(db) - } - - return context.collectionFactory -} - -// PackagePool returns instance of PackagePool -func (context *AptlyContext) PackagePool() aptly.PackagePool { - if context.packagePool == nil { - context.packagePool = files.NewPackagePool(context.Config().RootDir) - } - - return context.packagePool -} - -// GetPublishedStorage returns instance of PublishedStorage -func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage { - publishedStorage, ok := context.publishedStorages[name] - if !ok { - if name == "" { - publishedStorage = files.NewPublishedStorage(context.Config().RootDir) - } else if strings.HasPrefix(name, "s3:") { - params, ok := context.Config().S3PublishRoots[name[3:]] - if !ok { - Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:])) - } - - var err error - publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey, - params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass, - params.EncryptionMethod, params.PlusWorkaround) - if err != nil { - Fatal(err) - } - } else { - Fatal(fmt.Errorf("unknown published storage format: %v", name)) - } - context.publishedStorages[name] = publishedStorage - } - - return publishedStorage -} - -// UpdateFlags sets internal copy of flags in the context -func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) { - context.flags = flags -} +var context *ctx.AptlyContext // ShutdownContext shuts context down func ShutdownContext() { - if aptly.EnableDebug { - if context.fileMemProfile != nil { - pprof.WriteHeapProfile(context.fileMemProfile) - context.fileMemProfile.Close() - context.fileMemProfile = nil - } - if context.fileCPUProfile != nil { - pprof.StopCPUProfile() - context.fileCPUProfile.Close() - context.fileCPUProfile = nil - } - if context.fileMemProfile != nil { - context.fileMemProfile.Close() - context.fileMemProfile = nil - } - } - if context.database != nil { - context.database.Close() - context.database = nil - } - if context.downloader != nil { - context.downloader.Abort() - context.downloader = nil - } - if context.progress != nil { - context.progress.Shutdown() - context.progress = nil - } + context.Shutdown() } // CleanupContext does partial shutdown of context func CleanupContext() { - if context.downloader != nil { - context.downloader.Shutdown() - context.downloader = nil - } - if context.progress != nil { - context.progress.Shutdown() - context.progress = nil - } + context.Cleanup() } // InitContext initializes context with default settings @@ -318,60 +25,7 @@ func InitContext(flags *flag.FlagSet) error { panic("context already initialized") } - context = &AptlyContext{ - flags: flags, - globalFlags: flags, - dependencyOptions: -1, - publishedStorages: map[string]aptly.PublishedStorage{}, - } + context, err = ctx.NewContext(flags) - if aptly.EnableDebug { - cpuprofile := flags.Lookup("cpuprofile").Value.String() - if cpuprofile != "" { - context.fileCPUProfile, err = os.Create(cpuprofile) - if err != nil { - return err - } - pprof.StartCPUProfile(context.fileCPUProfile) - } - - memprofile := flags.Lookup("memprofile").Value.String() - if memprofile != "" { - context.fileMemProfile, err = os.Create(memprofile) - if err != nil { - return err - } - } - - memstats := flags.Lookup("memstats").Value.String() - if memstats != "" { - interval := flags.Lookup("meminterval").Value.Get().(time.Duration) - - context.fileMemStats, err = os.Create(memstats) - if err != nil { - return err - } - - context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n") - - go func() { - var stats runtime.MemStats - - start := time.Now().UnixNano() - - for { - runtime.ReadMemStats(&stats) - if context.fileMemStats != nil { - context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n", - (time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased)) - time.Sleep(interval) - } else { - break - } - } - }() - } - } - - return nil + return err } diff --git a/src/github.com/smira/aptly/cmd/db_cleanup.go b/src/github.com/smira/aptly/cmd/db_cleanup.go index f522ebee..40d5c5e4 100644 --- a/src/github.com/smira/aptly/cmd/db_cleanup.go +++ b/src/github.com/smira/aptly/cmd/db_cleanup.go @@ -20,14 +20,14 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error { // collect information about references packages... existingPackageRefs := deb.NewPackageRefList() - context.Progress().Printf("Loading mirrors, local repos and snapshots...\n") + context.Progress().Printf("Loading mirrors, local repos, snapshots and published repos...\n") err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error { err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) if err != nil { return err } if repo.RefList() != nil { - existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false) + existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true) } return nil }) @@ -41,7 +41,7 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error { return err } if repo.RefList() != nil { - existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false) + existingPackageRefs = existingPackageRefs.Merge(repo.RefList(), false, true) } return nil }) @@ -54,7 +54,25 @@ func aptlyDbCleanup(cmd *commander.Command, args []string) error { if err != nil { return err } - existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false) + existingPackageRefs = existingPackageRefs.Merge(snapshot.RefList(), false, true) + return nil + }) + if err != nil { + return err + } + + err = context.CollectionFactory().PublishedRepoCollection().ForEach(func(published *deb.PublishedRepo) error { + if published.SourceKind != "local" { + return nil + } + err := context.CollectionFactory().PublishedRepoCollection().LoadComplete(published, context.CollectionFactory()) + if err != nil { + return err + } + + for _, component := range published.Components() { + existingPackageRefs = existingPackageRefs.Merge(published.RefList(component), false, true) + } return nil }) if err != nil { diff --git a/src/github.com/smira/aptly/cmd/graph.go b/src/github.com/smira/aptly/cmd/graph.go index 28bbc969..552066da 100644 --- a/src/github.com/smira/aptly/cmd/graph.go +++ b/src/github.com/smira/aptly/cmd/graph.go @@ -2,7 +2,6 @@ package cmd import ( "bytes" - "code.google.com/p/gographviz" "fmt" "github.com/smira/aptly/deb" "github.com/smira/commander" @@ -10,7 +9,6 @@ import ( "io/ioutil" "os" "os/exec" - "strings" ) func aptlyGraph(cmd *commander.Command, args []string) error { @@ -21,121 +19,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - graph := gographviz.NewEscape() - graph.SetDir(true) - graph.SetName("aptly") - - existingNodes := map[string]bool{} - - fmt.Printf("Loading mirrors...\n") - - err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error { - err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo) - if err != nil { - return err - } - - graph.AddNode("aptly", repo.UUID, map[string]string{ - "shape": "Mrecord", - "style": "filled", - "fillcolor": "darkgoldenrod1", - "label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}", - repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "), - strings.Join(repo.Architectures, ", "), repo.NumPackages()), - }) - existingNodes[repo.UUID] = true - return nil - }) - - if err != nil { - return err - } - - fmt.Printf("Loading local repos...\n") - - err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error { - err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo) - if err != nil { - return err - } - - graph.AddNode("aptly", repo.UUID, map[string]string{ - "shape": "Mrecord", - "style": "filled", - "fillcolor": "mediumseagreen", - "label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}", - repo.Name, repo.Comment, repo.NumPackages()), - }) - existingNodes[repo.UUID] = true - return nil - }) - - if err != nil { - return err - } - - fmt.Printf("Loading snapshots...\n") - - context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error { - existingNodes[snapshot.UUID] = true - return nil - }) - - err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error { - err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot) - if err != nil { - return err - } - - description := snapshot.Description - if snapshot.SourceKind == "repo" { - description = "Snapshot from repo" - } - - graph.AddNode("aptly", snapshot.UUID, map[string]string{ - "shape": "Mrecord", - "style": "filled", - "fillcolor": "cadetblue1", - "label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()), - }) - - if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" { - for _, uuid := range snapshot.SourceIDs { - _, exists := existingNodes[uuid] - if exists { - graph.AddEdge(uuid, snapshot.UUID, true, nil) - } - } - } - return nil - }) - - if err != nil { - return err - } - - fmt.Printf("Loading published repos...\n") - - context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error { - graph.AddNode("aptly", repo.UUID, map[string]string{ - "shape": "Mrecord", - "style": "filled", - "fillcolor": "darkolivegreen1", - "label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, - strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")), - }) - - for _, uuid := range repo.Sources { - _, exists := existingNodes[uuid] - if exists { - graph.AddEdge(uuid, repo.UUID, true, nil) - } - } - - return nil - }) - fmt.Printf("Generating graph...\n") + graph, err := deb.BuildGraph(context.CollectionFactory()) + if err != nil { + return err + } buf := bytes.NewBufferString(graph.String()) diff --git a/src/github.com/smira/aptly/cmd/mirror_create.go b/src/github.com/smira/aptly/cmd/mirror_create.go index 27a7dcc0..299ca6e5 100644 --- a/src/github.com/smira/aptly/cmd/mirror_create.go +++ b/src/github.com/smira/aptly/cmd/mirror_create.go @@ -16,8 +16,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.flags, "with-sources") - downloadUdebs := context.flags.Lookup("with-udebs").Value.Get().(bool) + downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources") + downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool) var ( mirrorName, archiveURL, distribution string @@ -40,8 +40,9 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to create mirror: %s", err) } - repo.Filter = context.flags.Lookup("filter").Value.String() - repo.FilterWithDeps = context.flags.Lookup("filter-with-deps").Value.Get().(bool) + repo.Filter = context.Flags().Lookup("filter").Value.String() + repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool) + repo.SkipComponentCheck = context.Flags().Lookup("force-components").Value.Get().(bool) if repo.Filter != "" { _, err = query.Parse(repo.Filter) @@ -50,7 +51,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error { } } - verifier, err := getVerifier(context.flags) + verifier, err := getVerifier(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG verifier: %s", err) } @@ -95,6 +96,7 @@ Example: cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)") cmd.Flag.String("filter", "", "filter packages in mirror") cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well") + cmd.Flag.Bool("force-components", false, "(only with component list) skip check that requested components are listed in Release file") cmd.Flag.Var(&keyRingsFlag{}, "keyring", "gpg keyring to use when verifying Release file (could be specified multiple times)") return cmd diff --git a/src/github.com/smira/aptly/cmd/mirror_drop.go b/src/github.com/smira/aptly/cmd/mirror_drop.go index ba9e8281..9f46461a 100644 --- a/src/github.com/smira/aptly/cmd/mirror_drop.go +++ b/src/github.com/smira/aptly/cmd/mirror_drop.go @@ -25,7 +25,7 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to drop: %s", err) } - force := context.flags.Lookup("force").Value.Get().(bool) + force := context.Flags().Lookup("force").Value.Get().(bool) if !force { snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo) diff --git a/src/github.com/smira/aptly/cmd/mirror_edit.go b/src/github.com/smira/aptly/cmd/mirror_edit.go index 0cea1034..b1ff2dba 100644 --- a/src/github.com/smira/aptly/cmd/mirror_edit.go +++ b/src/github.com/smira/aptly/cmd/mirror_edit.go @@ -24,7 +24,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to edit: %s", err) } - context.flags.Visit(func(flag *flag.Flag) { + context.Flags().Visit(func(flag *flag.Flag) { switch flag.Name { case "filter": repo.Filter = flag.Value.String() @@ -48,7 +48,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error { } } - if context.globalFlags.Lookup("architectures").Value.String() != "" { + if context.GlobalFlags().Lookup("architectures").Value.String() != "" { repo.Architectures = context.ArchitecturesList() err = repo.Fetch(context.Downloader(), nil) diff --git a/src/github.com/smira/aptly/cmd/mirror_list.go b/src/github.com/smira/aptly/cmd/mirror_list.go index 933a5b6d..e2d7f1ed 100644 --- a/src/github.com/smira/aptly/cmd/mirror_list.go +++ b/src/github.com/smira/aptly/cmd/mirror_list.go @@ -28,6 +28,8 @@ func aptlyMirrorList(cmd *commander.Command, args []string) error { return nil }) + context.CloseDatabase() + sort.Strings(repos) if raw { diff --git a/src/github.com/smira/aptly/cmd/mirror_show.go b/src/github.com/smira/aptly/cmd/mirror_show.go index d2fea81f..4f7a308a 100644 --- a/src/github.com/smira/aptly/cmd/mirror_show.go +++ b/src/github.com/smira/aptly/cmd/mirror_show.go @@ -66,7 +66,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error { fmt.Printf("%s: %s\n", k, repo.Meta[k]) } - withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) + withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool) if withPackages { if repo.LastDownloadDate.IsZero() { fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n") diff --git a/src/github.com/smira/aptly/cmd/mirror_update.go b/src/github.com/smira/aptly/cmd/mirror_update.go index 5b5cc285..b2df188e 100644 --- a/src/github.com/smira/aptly/cmd/mirror_update.go +++ b/src/github.com/smira/aptly/cmd/mirror_update.go @@ -31,7 +31,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to update: %s", err) } - force := context.flags.Lookup("force").Value.Get().(bool) + force := context.Flags().Lookup("force").Value.Get().(bool) if !force { err = repo.CheckLock() if err != nil { @@ -39,9 +39,9 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error { } } - ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool) + ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool) - verifier, err := getVerifier(context.flags) + verifier, err := getVerifier(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG verifier: %s", err) } diff --git a/src/github.com/smira/aptly/cmd/package_search.go b/src/github.com/smira/aptly/cmd/package_search.go index 8c910548..27a159cb 100644 --- a/src/github.com/smira/aptly/cmd/package_search.go +++ b/src/github.com/smira/aptly/cmd/package_search.go @@ -21,6 +21,10 @@ func aptlyPackageSearch(cmd *commander.Command, args []string) error { } result := q.Query(context.CollectionFactory().PackageCollection()) + if result.Len() == 0 { + return fmt.Errorf("no results") + } + result.ForEach(func(p *deb.Package) error { context.Progress().Printf("%s\n", p) return nil diff --git a/src/github.com/smira/aptly/cmd/package_show.go b/src/github.com/smira/aptly/cmd/package_show.go index d544f0a8..67e5efe5 100644 --- a/src/github.com/smira/aptly/cmd/package_show.go +++ b/src/github.com/smira/aptly/cmd/package_show.go @@ -72,15 +72,15 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to show: %s", err) } - withFiles := context.flags.Lookup("with-files").Value.Get().(bool) - withReferences := context.flags.Lookup("with-references").Value.Get().(bool) + withFiles := context.Flags().Lookup("with-files").Value.Get().(bool) + withReferences := context.Flags().Lookup("with-references").Value.Get().(bool) w := bufio.NewWriter(os.Stdout) result := q.Query(context.CollectionFactory().PackageCollection()) err = result.ForEach(func(p *deb.Package) error { - p.Stanza().WriteTo(w) + p.Stanza().WriteTo(w, p.IsSource, false) w.Flush() fmt.Printf("\n") @@ -116,7 +116,7 @@ func makeCmdPackageShow() *commander.Command { cmd := &commander.Command{ Run: aptlyPackageShow, UsageLine: "show ", - Short: "show details about packages matcing query", + Short: "show details about packages matching query", Long: ` Command shows displays detailed meta-information about packages matching query. Information from Debian control file is displayed. diff --git a/src/github.com/smira/aptly/cmd/publish.go b/src/github.com/smira/aptly/cmd/publish.go index 1ad8b163..c468caba 100644 --- a/src/github.com/smira/aptly/cmd/publish.go +++ b/src/github.com/smira/aptly/cmd/publish.go @@ -4,7 +4,6 @@ import ( "github.com/smira/aptly/utils" "github.com/smira/commander" "github.com/smira/flag" - "strings" ) func getSigner(flags *flag.FlagSet) (utils.Signer, error) { @@ -16,6 +15,7 @@ func getSigner(flags *flag.FlagSet) (utils.Signer, error) { signer.SetKey(flags.Lookup("gpg-key").Value.String()) signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String()) signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String()) + signer.SetBatch(flags.Lookup("batch").Value.Get().(bool)) err := signer.Init() if err != nil { @@ -26,20 +26,6 @@ func getSigner(flags *flag.FlagSet) (utils.Signer, error) { } -func parsePrefix(param string) (storage, prefix string) { - i := strings.LastIndex(param, ":") - if i != -1 { - storage = param[:i] - prefix = param[i+1:] - if prefix == "" { - prefix = "." - } - } else { - prefix = param - } - return -} - func makeCmdPublish() *commander.Command { return &commander.Command{ UsageLine: "publish", diff --git a/src/github.com/smira/aptly/cmd/publish_drop.go b/src/github.com/smira/aptly/cmd/publish_drop.go index 380a26e7..0bedc7d5 100644 --- a/src/github.com/smira/aptly/cmd/publish_drop.go +++ b/src/github.com/smira/aptly/cmd/publish_drop.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/smira/aptly/deb" "github.com/smira/commander" ) @@ -19,7 +20,7 @@ func aptlyPublishDrop(cmd *commander.Command, args []string) error { param = args[1] } - storage, prefix := parsePrefix(param) + storage, prefix := deb.ParsePrefix(param) err = context.CollectionFactory().PublishedRepoCollection().Remove(context, storage, prefix, distribution, context.CollectionFactory(), context.Progress()) diff --git a/src/github.com/smira/aptly/cmd/publish_list.go b/src/github.com/smira/aptly/cmd/publish_list.go index 20d89bcc..69e5b4c6 100644 --- a/src/github.com/smira/aptly/cmd/publish_list.go +++ b/src/github.com/smira/aptly/cmd/publish_list.go @@ -36,6 +36,8 @@ func aptlyPublishList(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to load list of repos: %s", err) } + context.CloseDatabase() + sort.Strings(published) if raw { diff --git a/src/github.com/smira/aptly/cmd/publish_repo.go b/src/github.com/smira/aptly/cmd/publish_repo.go index 405c5970..892d7a62 100644 --- a/src/github.com/smira/aptly/cmd/publish_repo.go +++ b/src/github.com/smira/aptly/cmd/publish_repo.go @@ -39,6 +39,7 @@ Example: cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") + cmd.Flag.Bool("batch", false, "run GPG with detached tty") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("label", "", "label to publish") diff --git a/src/github.com/smira/aptly/cmd/publish_snapshot.go b/src/github.com/smira/aptly/cmd/publish_snapshot.go index 92877522..b207c5b1 100644 --- a/src/github.com/smira/aptly/cmd/publish_snapshot.go +++ b/src/github.com/smira/aptly/cmd/publish_snapshot.go @@ -13,7 +13,7 @@ import ( func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { var err error - components := strings.Split(context.flags.Lookup("component").Value.String(), ",") + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") if len(args) < len(components) || len(args) > len(components)+1 { cmd.Usage() @@ -27,7 +27,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { } else { param = "" } - storage, prefix := parsePrefix(param) + storage, prefix := deb.ParsePrefix(param) var ( sources = []interface{}{} @@ -110,7 +110,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { panic("unknown command") } - distribution := context.flags.Lookup("distribution").Value.String() + distribution := context.Flags().Lookup("distribution").Value.String() published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory()) if err != nil { @@ -125,12 +125,12 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate) } - signer, err := getSigner(context.flags) + signer, err := getSigner(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG signer: %s", err) } - forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) + forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool) if forceOverwrite { context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + "the same package pool.\n") @@ -201,6 +201,7 @@ Example: cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") + cmd.Flag.Bool("batch", false, "run GPG with detached tty") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("origin", "", "origin name to publish") cmd.Flag.String("label", "", "label to publish") diff --git a/src/github.com/smira/aptly/cmd/publish_switch.go b/src/github.com/smira/aptly/cmd/publish_switch.go index 21b97eca..ea3402d0 100644 --- a/src/github.com/smira/aptly/cmd/publish_switch.go +++ b/src/github.com/smira/aptly/cmd/publish_switch.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "github.com/smira/aptly/deb" + "github.com/smira/aptly/utils" "github.com/smira/commander" "github.com/smira/flag" "strings" @@ -11,7 +12,7 @@ import ( func aptlyPublishSwitch(cmd *commander.Command, args []string) error { var err error - components := strings.Split(context.flags.Lookup("component").Value.String(), ",") + components := strings.Split(context.Flags().Lookup("component").Value.String(), ",") if len(args) < len(components)+1 || len(args) > len(components)+2 { cmd.Usage() @@ -33,7 +34,7 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { names = args[1:] } - storage, prefix := parsePrefix(param) + storage, prefix := deb.ParsePrefix(param) var published *deb.PublishedRepo @@ -61,6 +62,10 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { } for i, component := range components { + if !utils.StrSliceHasItem(publishedComponents, component) { + return fmt.Errorf("unable to switch: component %s is not in published repository", component) + } + snapshot, err = context.CollectionFactory().SnapshotCollection().ByName(names[i]) if err != nil { return fmt.Errorf("unable to switch: %s", err) @@ -74,12 +79,12 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error { published.UpdateSnapshot(component, snapshot) } - signer, err := getSigner(context.flags) + signer, err := getSigner(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG signer: %s", err) } - forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) + forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool) if forceOverwrite { context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + "the same package pool.\n") @@ -112,7 +117,7 @@ func makeCmdPublishSwitch() *commander.Command { UsageLine: "switch [[:]] ", Short: "update published repository by switching to new snapshot", Long: ` -Command switches in-place published repository with new snapshot contents. All +Command switches in-place published snapshots with new snapshot contents. All publishing parameters are preserved (architecture list, distribution, component). @@ -120,11 +125,14 @@ For multiple component repositories, flag -component should be given with list of components to update. Corresponding snapshots should be given in the same order, e.g.: - aptly publish update -component=main,contrib wheezy wh-main wh-contrib + aptly publish switch -component=main,contrib wheezy wh-main wh-contrib Example: - $ aptly publish update wheezy ppa wheezy-7.5 + $ aptly publish switch wheezy ppa wheezy-7.5 + +This command would switch published repository (with one component) named ppa/wheezy +(prefix ppa, dsitribution wheezy to new snapshot wheezy-7.5). `, Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError), } @@ -133,6 +141,7 @@ Example: cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") + cmd.Flag.Bool("batch", false, "run GPG with detached tty") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") diff --git a/src/github.com/smira/aptly/cmd/publish_update.go b/src/github.com/smira/aptly/cmd/publish_update.go index 0629acdc..93273a45 100644 --- a/src/github.com/smira/aptly/cmd/publish_update.go +++ b/src/github.com/smira/aptly/cmd/publish_update.go @@ -20,7 +20,7 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error { if len(args) == 2 { param = args[1] } - storage, prefix := parsePrefix(param) + storage, prefix := deb.ParsePrefix(param) var published *deb.PublishedRepo @@ -43,12 +43,12 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error { published.UpdateLocalRepo(component) } - signer, err := getSigner(context.flags) + signer, err := getSigner(context.Flags()) if err != nil { return fmt.Errorf("unable to initialize GPG signer: %s", err) } - forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) + forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool) if forceOverwrite { context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + "the same package pool.\n") @@ -100,6 +100,7 @@ Example: cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)") cmd.Flag.String("passphrase", "", "GPG passhprase for the key (warning: could be insecure)") cmd.Flag.String("passphrase-file", "", "GPG passhprase-file for the key (warning: could be insecure)") + cmd.Flag.Bool("batch", false, "run GPG with detached tty") cmd.Flag.Bool("skip-signing", false, "don't sign Release files with GPG") cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch") diff --git a/src/github.com/smira/aptly/cmd/repo_add.go b/src/github.com/smira/aptly/cmd/repo_add.go index 7626efc0..7529a105 100644 --- a/src/github.com/smira/aptly/cmd/repo_add.go +++ b/src/github.com/smira/aptly/cmd/repo_add.go @@ -2,14 +2,12 @@ package cmd import ( "fmt" + "github.com/smira/aptly/aptly" "github.com/smira/aptly/deb" "github.com/smira/aptly/utils" "github.com/smira/commander" "github.com/smira/flag" "os" - "path/filepath" - "sort" - "strings" ) func aptlyRepoAdd(cmd *commander.Command, args []string) error { @@ -40,151 +38,22 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to load packages: %s", err) } - forceReplace := context.flags.Lookup("force-replace").Value.Get().(bool) + forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool) - packageFiles := []string{} - failedFiles := []string{} + var packageFiles, failedFiles []string - for _, location := range args[1:] { - info, err2 := os.Stat(location) - if err2 != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to process %s: %s@|", location, err2) - failedFiles = append(failedFiles, location) - continue - } - if info.IsDir() { - err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error { - if err3 != nil { - return err3 - } - if info.IsDir() { - return nil - } - - if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || - strings.HasSuffix(info.Name(), ".dsc") { - packageFiles = append(packageFiles, path) - } - - return nil - }) - } else { - if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || - strings.HasSuffix(info.Name(), ".dsc") { - packageFiles = append(packageFiles, location) - } else { - context.Progress().ColoredPrintf("@y[!]@| @!Unknwon file extenstion: %s@|", location) - failedFiles = append(failedFiles, location) - continue - } - } + packageFiles, failedFiles, err = deb.CollectPackageFiles(args[1:], &aptly.ConsoleResultReporter{Progress: context.Progress()}) + if err != nil { + return fmt.Errorf("unable to collect package files: %s", err) } - processedFiles := []string{} - sort.Strings(packageFiles) + var processedFiles, failedFiles2 []string - if forceReplace { - list.PrepareIndex() - } - - for _, file := range packageFiles { - var ( - stanza deb.Stanza - p *deb.Package - ) - - candidateProcessedFiles := []string{} - isSourcePackage := strings.HasSuffix(file, ".dsc") - isUdebPackage := strings.HasSuffix(file, ".udeb") - - if isSourcePackage { - stanza, err = deb.GetControlFileFromDsc(file, verifier) - - if err == nil { - stanza["Package"] = stanza["Source"] - delete(stanza, "Source") - - p, err = deb.NewSourcePackageFromControlFile(stanza) - } - } else { - stanza, err = deb.GetControlFileFromDeb(file) - if isUdebPackage { - p = deb.NewUdebPackageFromControlFile(stanza) - } else { - p = deb.NewPackageFromControlFile(stanza) - } - } - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to read file %s: %s@|", file, err) - failedFiles = append(failedFiles, file) - continue - } - - var checksums utils.ChecksumInfo - checksums, err = utils.ChecksumsForFile(file) - if err != nil { - return err - } - - if isSourcePackage { - p.UpdateFiles(append(p.Files(), deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums})) - } else { - p.UpdateFiles([]deb.PackageFile{deb.PackageFile{Filename: filepath.Base(file), Checksums: checksums}}) - } - - err = context.PackagePool().Import(file, checksums.MD5) - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", file, err) - failedFiles = append(failedFiles, file) - continue - } - - candidateProcessedFiles = append(candidateProcessedFiles, file) - - // go over all files, except for the last one (.dsc/.deb itself) - for _, f := range p.Files() { - if filepath.Base(f.Filename) == filepath.Base(file) { - continue - } - sourceFile := filepath.Join(filepath.Dir(file), filepath.Base(f.Filename)) - err = context.PackagePool().Import(sourceFile, f.Checksums.MD5) - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to import file %s into pool: %s@|", sourceFile, err) - failedFiles = append(failedFiles, file) - break - } - - candidateProcessedFiles = append(candidateProcessedFiles, sourceFile) - } - if err != nil { - // some files haven't been imported - continue - } - - err = context.CollectionFactory().PackageCollection().Update(p) - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to save package %s: %s@|", p, err) - failedFiles = append(failedFiles, file) - continue - } - - if forceReplace { - conflictingPackages := list.Search(deb.Dependency{Pkg: p.Name, Version: p.Version, Architecture: p.Architecture}, true) - for _, cp := range conflictingPackages { - context.Progress().ColoredPrintf("@r[-]@| %s removed due to conflict with package being added", cp) - list.Remove(cp) - } - } - - err = list.Add(p) - if err != nil { - context.Progress().ColoredPrintf("@y[!]@| @!Unable to add package to repo %s: %s@|", p, err) - failedFiles = append(failedFiles, file) - continue - } - - context.Progress().ColoredPrintf("@g[+]@| %s added@|", p) - processedFiles = append(processedFiles, candidateProcessedFiles...) + processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(), + context.CollectionFactory().PackageCollection(), &aptly.ConsoleResultReporter{Progress: context.Progress()}) + failedFiles = append(failedFiles, failedFiles2...) + if err != nil { + return fmt.Errorf("unable to import package files: %s", err) } repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) @@ -194,7 +63,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to save: %s", err) } - if context.flags.Lookup("remove-files").Value.Get().(bool) { + if context.Flags().Lookup("remove-files").Value.Get().(bool) { processedFiles = utils.StrSliceDeduplicate(processedFiles) for _, file := range processedFiles { diff --git a/src/github.com/smira/aptly/cmd/repo_create.go b/src/github.com/smira/aptly/cmd/repo_create.go index f62f5c35..67540130 100644 --- a/src/github.com/smira/aptly/cmd/repo_create.go +++ b/src/github.com/smira/aptly/cmd/repo_create.go @@ -14,9 +14,9 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String()) - repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() - repo.DefaultComponent = context.flags.Lookup("component").Value.String() + repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String()) + repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String() + repo.DefaultComponent = context.Flags().Lookup("component").Value.String() err = context.CollectionFactory().LocalRepoCollection().Add(repo) if err != nil { diff --git a/src/github.com/smira/aptly/cmd/repo_drop.go b/src/github.com/smira/aptly/cmd/repo_drop.go index b95ab6c0..a2bd4ad5 100644 --- a/src/github.com/smira/aptly/cmd/repo_drop.go +++ b/src/github.com/smira/aptly/cmd/repo_drop.go @@ -34,7 +34,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to drop: local repo is published") } - force := context.flags.Lookup("force").Value.Get().(bool) + force := context.Flags().Lookup("force").Value.Get().(bool) if !force { snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo) diff --git a/src/github.com/smira/aptly/cmd/repo_edit.go b/src/github.com/smira/aptly/cmd/repo_edit.go index 6177be43..648382b7 100644 --- a/src/github.com/smira/aptly/cmd/repo_edit.go +++ b/src/github.com/smira/aptly/cmd/repo_edit.go @@ -23,16 +23,16 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to edit: %s", err) } - if context.flags.Lookup("comment").Value.String() != "" { - repo.Comment = context.flags.Lookup("comment").Value.String() + if context.Flags().Lookup("comment").Value.String() != "" { + repo.Comment = context.Flags().Lookup("comment").Value.String() } - if context.flags.Lookup("distribution").Value.String() != "" { - repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() + if context.Flags().Lookup("distribution").Value.String() != "" { + repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String() } - if context.flags.Lookup("component").Value.String() != "" { - repo.DefaultComponent = context.flags.Lookup("component").Value.String() + if context.Flags().Lookup("component").Value.String() != "" { + repo.DefaultComponent = context.Flags().Lookup("component").Value.String() } err = context.CollectionFactory().LocalRepoCollection().Update(repo) diff --git a/src/github.com/smira/aptly/cmd/repo_list.go b/src/github.com/smira/aptly/cmd/repo_list.go index 677a1357..6c2452b3 100644 --- a/src/github.com/smira/aptly/cmd/repo_list.go +++ b/src/github.com/smira/aptly/cmd/repo_list.go @@ -33,6 +33,8 @@ func aptlyRepoList(cmd *commander.Command, args []string) error { return nil }) + context.CloseDatabase() + sort.Strings(repos) if raw { diff --git a/src/github.com/smira/aptly/cmd/repo_move.go b/src/github.com/smira/aptly/cmd/repo_move.go index 7a571821..4eaeb259 100644 --- a/src/github.com/smira/aptly/cmd/repo_move.go +++ b/src/github.com/smira/aptly/cmd/repo_move.go @@ -87,7 +87,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error { var architecturesList []string - withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) + withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool) if withDeps { dstList.PrepareIndex() @@ -145,7 +145,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to %s: %s", command, err) } - if context.flags.Lookup("dry-run").Value.Get().(bool) { + if context.Flags().Lookup("dry-run").Value.Get().(bool) { context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") } else { dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList)) diff --git a/src/github.com/smira/aptly/cmd/repo_remove.go b/src/github.com/smira/aptly/cmd/repo_remove.go index 9eef3eb4..65a4ebb2 100644 --- a/src/github.com/smira/aptly/cmd/repo_remove.go +++ b/src/github.com/smira/aptly/cmd/repo_remove.go @@ -54,7 +54,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error { return nil }) - if context.flags.Lookup("dry-run").Value.Get().(bool) { + if context.Flags().Lookup("dry-run").Value.Get().(bool) { context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") } else { repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) diff --git a/src/github.com/smira/aptly/cmd/repo_show.go b/src/github.com/smira/aptly/cmd/repo_show.go index e5e7ef82..a6ee9804 100644 --- a/src/github.com/smira/aptly/cmd/repo_show.go +++ b/src/github.com/smira/aptly/cmd/repo_show.go @@ -31,7 +31,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error { fmt.Printf("Default Component: %s\n", repo.DefaultComponent) fmt.Printf("Number of packages: %d\n", repo.NumPackages()) - withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) + withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool) if withPackages { ListPackagesRefList(repo.RefList()) } diff --git a/src/github.com/smira/aptly/cmd/run.go b/src/github.com/smira/aptly/cmd/run.go index 6ac45138..1e782225 100644 --- a/src/github.com/smira/aptly/cmd/run.go +++ b/src/github.com/smira/aptly/cmd/run.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + ctx "github.com/smira/aptly/context" "github.com/smira/commander" ) @@ -9,7 +10,7 @@ import ( func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) { defer func() { if r := recover(); r != nil { - fatal, ok := r.(*FatalError) + fatal, ok := r.(*ctx.FatalError) if !ok { panic(r) } @@ -22,13 +23,13 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode flags, args, err := cmd.ParseFlags(cmdArgs) if err != nil { - Fatal(err) + ctx.Fatal(err) } if initContext { err = InitContext(flags) if err != nil { - Fatal(err) + ctx.Fatal(err) } defer ShutdownContext() } @@ -37,7 +38,7 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode err = cmd.Dispatch(args) if err != nil { - Fatal(err) + ctx.Fatal(err) } return diff --git a/src/github.com/smira/aptly/cmd/serve.go b/src/github.com/smira/aptly/cmd/serve.go index a2eef752..d1c19e03 100644 --- a/src/github.com/smira/aptly/cmd/serve.go +++ b/src/github.com/smira/aptly/cmd/serve.go @@ -27,7 +27,7 @@ func aptlyServe(cmd *commander.Command, args []string) error { return nil } - listen := context.flags.Lookup("listen").Value.String() + listen := context.Flags().Lookup("listen").Value.String() listenHost, listenPort, err := net.SplitHostPort(listen) diff --git a/src/github.com/smira/aptly/cmd/snapshot_diff.go b/src/github.com/smira/aptly/cmd/snapshot_diff.go index e643eb47..8da16448 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_diff.go +++ b/src/github.com/smira/aptly/cmd/snapshot_diff.go @@ -13,7 +13,7 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - onlyMatching := context.flags.Lookup("only-matching").Value.Get().(bool) + onlyMatching := context.Flags().Lookup("only-matching").Value.Get().(bool) // Load snapshot snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) diff --git a/src/github.com/smira/aptly/cmd/snapshot_drop.go b/src/github.com/smira/aptly/cmd/snapshot_drop.go index daf6a1d2..4cbee1fa 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_drop.go +++ b/src/github.com/smira/aptly/cmd/snapshot_drop.go @@ -35,7 +35,7 @@ func aptlySnapshotDrop(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to drop: snapshot is published") } - force := context.flags.Lookup("force").Value.Get().(bool) + force := context.Flags().Lookup("force").Value.Get().(bool) if !force { snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot) if len(snapshots) > 0 { diff --git a/src/github.com/smira/aptly/cmd/snapshot_filter.go b/src/github.com/smira/aptly/cmd/snapshot_filter.go index ce91fdc6..f07904b8 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_filter.go +++ b/src/github.com/smira/aptly/cmd/snapshot_filter.go @@ -17,7 +17,7 @@ func aptlySnapshotFilter(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) + withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool) // Load snapshot source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) diff --git a/src/github.com/smira/aptly/cmd/snapshot_list.go b/src/github.com/smira/aptly/cmd/snapshot_list.go index 1ca2758d..06ba631c 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_list.go +++ b/src/github.com/smira/aptly/cmd/snapshot_list.go @@ -4,49 +4,8 @@ import ( "fmt" "github.com/smira/aptly/deb" "github.com/smira/commander" - "sort" ) -// Snapshot sorting methods -const ( - SortName = iota - SortTime -) - -type snapshotListToSort struct { - list []*deb.Snapshot - sortMethod int -} - -func parseSortMethod(sortMethod string) (int, error) { - switch sortMethod { - case "time", "Time": - return SortTime, nil - case "name", "Name": - return SortName, nil - } - - return -1, fmt.Errorf("sorting method \"%s\" unknown", sortMethod) -} - -func (s snapshotListToSort) Swap(i, j int) { - s.list[i], s.list[j] = s.list[j], s.list[i] -} - -func (s snapshotListToSort) Less(i, j int) bool { - switch s.sortMethod { - case SortName: - return s.list[i].Name < s.list[j].Name - case SortTime: - return s.list[i].CreatedAt.Before(s.list[j].CreatedAt) - } - panic("unknown sort method") -} - -func (s snapshotListToSort) Len() int { - return len(s.list) -} - func aptlySnapshotList(cmd *commander.Command, args []string) error { var err error if len(args) != 0 { @@ -57,33 +16,24 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error { raw := cmd.Flag.Lookup("raw").Value.Get().(bool) sortMethodString := cmd.Flag.Lookup("sort").Value.Get().(string) - snapshotsToSort := &snapshotListToSort{} - snapshotsToSort.list = make([]*deb.Snapshot, context.CollectionFactory().SnapshotCollection().Len()) - snapshotsToSort.sortMethod, err = parseSortMethod(sortMethodString) - if err != nil { - return err - } - - i := 0 - context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error { - snapshotsToSort.list[i] = snapshot - i++ - - return nil - }) - - sort.Sort(snapshotsToSort) + collection := context.CollectionFactory().SnapshotCollection() if raw { - for _, snapshot := range snapshotsToSort.list { + collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error { fmt.Printf("%s\n", snapshot.Name) - } + return nil + }) } else { - if len(snapshotsToSort.list) > 0 { + if collection.Len() > 0 { fmt.Printf("List of snapshots:\n") - for _, snapshot := range snapshotsToSort.list { + err = collection.ForEachSorted(sortMethodString, func(snapshot *deb.Snapshot) error { fmt.Printf(" * %s\n", snapshot.String()) + return nil + }) + + if err != nil { + return err } fmt.Printf("\nTo get more information about snapshot, run `aptly snapshot show `.\n") @@ -91,8 +41,8 @@ func aptlySnapshotList(cmd *commander.Command, args []string) error { fmt.Printf("\nNo snapshots found, create one with `aptly snapshot create...`.\n") } } - return err + return err } func makeCmdSnapshotList() *commander.Command { diff --git a/src/github.com/smira/aptly/cmd/snapshot_merge.go b/src/github.com/smira/aptly/cmd/snapshot_merge.go index b2480bfe..73b53a70 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_merge.go +++ b/src/github.com/smira/aptly/cmd/snapshot_merge.go @@ -28,22 +28,22 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error { } } - latest := context.flags.Lookup("latest").Value.Get().(bool) - noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) + latest := context.Flags().Lookup("latest").Value.Get().(bool) + noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool) - if noRemove && latest { - return fmt.Errorf("-no-remove and -latest can't be specified together") - } + if noRemove && latest { + return fmt.Errorf("-no-remove and -latest can't be specified together") + } overrideMatching := !latest && !noRemove result := sources[0].RefList() for i := 1; i < len(sources); i++ { - result = result.Merge(sources[i].RefList(), overrideMatching) + result = result.Merge(sources[i].RefList(), overrideMatching, false) } if latest { - deb.FilterLatestRefs(result) + result.FilterLatestRefs() } sourceDescription := make([]string, len(sources)) @@ -84,7 +84,7 @@ Example: } cmd.Flag.Bool("latest", false, "use only the latest version of each package") - cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages") + cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages") return cmd } diff --git a/src/github.com/smira/aptly/cmd/snapshot_pull.go b/src/github.com/smira/aptly/cmd/snapshot_pull.go index 7da20598..db4983d0 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_pull.go +++ b/src/github.com/smira/aptly/cmd/snapshot_pull.go @@ -17,9 +17,9 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { return commander.ErrCommandError } - noDeps := context.flags.Lookup("no-deps").Value.Get().(bool) - noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) - allMatches := context.flags.Lookup("all-matches").Value.Get().(bool) + noDeps := context.Flags().Lookup("no-deps").Value.Get().(bool) + noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool) + allMatches := context.Flags().Lookup("all-matches").Value.Get().(bool) // Load snapshot snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) @@ -91,7 +91,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { return fmt.Errorf("unable to parse query: %s", err) } // Add architecture filter - queries[i] = &deb.AndQuery{queries[i], archQuery} + queries[i] = &deb.AndQuery{L: queries[i], R: archQuery} } // Filter with dependencies as requested @@ -129,7 +129,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error { }) alreadySeen = nil - if context.flags.Lookup("dry-run").Value.Get().(bool) { + if context.Flags().Lookup("dry-run").Value.Get().(bool) { context.Progress().Printf("\nNot creating snapshot, as dry run was requested.\n") } else { // Create snapshot diff --git a/src/github.com/smira/aptly/cmd/snapshot_search.go b/src/github.com/smira/aptly/cmd/snapshot_search.go index 5768d65e..d1aee367 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_search.go +++ b/src/github.com/smira/aptly/cmd/snapshot_search.go @@ -73,7 +73,7 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error return fmt.Errorf("unable to search: %s", err) } - withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) + withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool) architecturesList := []string{} if withDeps { diff --git a/src/github.com/smira/aptly/cmd/snapshot_show.go b/src/github.com/smira/aptly/cmd/snapshot_show.go index 95bcc994..6e275e0e 100644 --- a/src/github.com/smira/aptly/cmd/snapshot_show.go +++ b/src/github.com/smira/aptly/cmd/snapshot_show.go @@ -30,7 +30,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error { fmt.Printf("Description: %s\n", snapshot.Description) fmt.Printf("Number of packages: %d\n", snapshot.NumPackages()) - withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) + withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool) if withPackages { ListPackagesRefList(snapshot.RefList()) } diff --git a/src/github.com/smira/aptly/cmd/task_run.go b/src/github.com/smira/aptly/cmd/task_run.go index 0a51c347..0503997d 100644 --- a/src/github.com/smira/aptly/cmd/task_run.go +++ b/src/github.com/smira/aptly/cmd/task_run.go @@ -18,13 +18,15 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error { var text string cmdArgs := []string{} - if finfo, err := os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() { + var finfo os.FileInfo + if finfo, err = os.Stat(filename); os.IsNotExist(err) || finfo.IsDir() { return fmt.Errorf("no such file, %s\n", filename) } - fmt.Println("Reading file...\n") + fmt.Print("Reading file...\n\n") - file, err := os.Open(filename) + var file *os.File + file, err = os.Open(filename) if err != nil { return err @@ -80,6 +82,10 @@ func aptlyTaskRun(cmd *commander.Command, args []string) error { for i, command := range cmdList { if !commandErrored { + err := context.ReOpenDatabase() + if err != nil { + return fmt.Errorf("failed to reopen DB: %s", err) + } context.Progress().ColoredPrintf("@g%d) [Running]: %s@!", (i + 1), strings.Join(command, " ")) context.Progress().ColoredPrintf("\n@yBegin command output: ----------------------------@!") context.Progress().Flush() @@ -129,16 +135,16 @@ func makeCmdTaskRun() *commander.Command { UsageLine: "run -filename= | , , ...", Short: "run aptly tasks", Long: ` -Command helps origanise multiple aptly commands in one single aptly task, running as single thread. +Command helps organise multiple aptly commands in one single aptly task, running as single thread. Example: - $ aptly task run - > repo create local - > repo add local pkg1 - > publish repo local - > serve - > + $ aptly task run + > repo create local + > repo add local pkg1 + > publish repo local + > serve + > `, } diff --git a/src/github.com/smira/aptly/console/progress.go b/src/github.com/smira/aptly/console/progress.go index 772be48d..398add43 100644 --- a/src/github.com/smira/aptly/console/progress.go +++ b/src/github.com/smira/aptly/console/progress.go @@ -14,6 +14,8 @@ const ( codeHideProgress codeStop codeFlush + codeBarEnabled + codeBarDisabled ) type printTask struct { @@ -81,6 +83,8 @@ func (p *Progress) InitBar(count int64, isBytes bool) { p.bar.SetUnits(pb.U_BYTES) p.bar.ShowSpeed = true } + + p.queue <- printTask{code: codeBarEnabled} p.bar.Start() } } @@ -91,6 +95,7 @@ func (p *Progress) ShutdownBar() { return } p.bar.Finish() + p.queue <- printTask{code: codeBarDisabled} p.bar = nil p.queue <- printTask{code: codeHideProgress} } @@ -151,9 +156,15 @@ func (p *Progress) ColoredPrintf(msg string, a ...interface{}) { } func (p *Progress) worker() { + hasBar := false + for { task := <-p.queue switch task.code { + case codeBarEnabled: + hasBar = true + case codeBarDisabled: + hasBar = false case codePrint: if p.barShown { fmt.Print("\r\033[2K") @@ -161,7 +172,7 @@ func (p *Progress) worker() { } fmt.Print(task.message) case codeProgress: - if p.bar != nil { + if hasBar { fmt.Print("\r" + task.message) p.barShown = true } diff --git a/src/github.com/smira/aptly/console/terminal.go b/src/github.com/smira/aptly/console/terminal.go index 7d20b99d..5f414d2a 100644 --- a/src/github.com/smira/aptly/console/terminal.go +++ b/src/github.com/smira/aptly/console/terminal.go @@ -1,9 +1,7 @@ -// +build !freebsd - package console import ( - "code.google.com/p/go.crypto/ssh/terminal" + "golang.org/x/crypto/ssh/terminal" "syscall" ) diff --git a/src/github.com/smira/aptly/console/terminal_bsd.go b/src/github.com/smira/aptly/console/terminal_bsd.go deleted file mode 100644 index 759c1e8d..00000000 --- a/src/github.com/smira/aptly/console/terminal_bsd.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build freebsd - -package console - -// RunningOnTerminal checks whether stdout is terminal -// -// Stub for FreeBSD, until in go1.3 terminal.IsTerminal would start working for FreeBSD -func RunningOnTerminal() bool { - return false -} diff --git a/src/github.com/smira/aptly/context/context.go b/src/github.com/smira/aptly/context/context.go new file mode 100644 index 00000000..96fe1a14 --- /dev/null +++ b/src/github.com/smira/aptly/context/context.go @@ -0,0 +1,490 @@ +// Package context provides single entry to all resources +package context + +import ( + "fmt" + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/console" + "github.com/smira/aptly/database" + "github.com/smira/aptly/deb" + "github.com/smira/aptly/files" + "github.com/smira/aptly/http" + "github.com/smira/aptly/s3" + "github.com/smira/aptly/swift" + "github.com/smira/aptly/utils" + "github.com/smira/commander" + "github.com/smira/flag" + "os" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "sync" + "time" +) + +// AptlyContext is a common context shared by all commands +type AptlyContext struct { + sync.Mutex + + flags, globalFlags *flag.FlagSet + configLoaded bool + + progress aptly.Progress + downloader aptly.Downloader + database database.Storage + packagePool aptly.PackagePool + publishedStorages map[string]aptly.PublishedStorage + collectionFactory *deb.CollectionFactory + dependencyOptions int + architecturesList []string + // Debug features + fileCPUProfile *os.File + fileMemProfile *os.File + fileMemStats *os.File +} + +// Check interface +var _ aptly.PublishedStorageProvider = &AptlyContext{} + +// FatalError is type for panicking to abort execution with non-zero +// exit code and print meaningful explanation +type FatalError struct { + ReturnCode int + Message string +} + +// Fatal panics and aborts execution with exit code 1 +func Fatal(err error) { + returnCode := 1 + if err == commander.ErrFlagError || err == commander.ErrCommandError { + returnCode = 2 + } + panic(&FatalError{ReturnCode: returnCode, Message: err.Error()}) +} + +// Config loads and returns current configuration +func (context *AptlyContext) Config() *utils.ConfigStructure { + context.Lock() + defer context.Unlock() + + return context.config() +} + +func (context *AptlyContext) config() *utils.ConfigStructure { + if !context.configLoaded { + var err error + + configLocation := context.globalFlags.Lookup("config").Value.String() + if configLocation != "" { + err = utils.LoadConfig(configLocation, &utils.Config) + + if err != nil { + Fatal(err) + } + } else { + configLocations := []string{ + filepath.Join(os.Getenv("HOME"), ".aptly.conf"), + "/etc/aptly.conf", + } + + for _, configLocation := range configLocations { + err = utils.LoadConfig(configLocation, &utils.Config) + if err == nil { + break + } + if !os.IsNotExist(err) { + Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err)) + } + } + + if err != nil { + fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0]) + utils.SaveConfig(configLocations[0], &utils.Config) + } + } + + context.configLoaded = true + + } + return &utils.Config +} + +// LookupOption checks boolean flag with default (usually config) and command-line +// setting +func (context *AptlyContext) LookupOption(defaultValue bool, name string) (result bool) { + context.Lock() + defer context.Unlock() + + return context.lookupOption(defaultValue, name) +} + +func (context *AptlyContext) lookupOption(defaultValue bool, name string) (result bool) { + result = defaultValue + + if context.globalFlags.IsSet(name) { + result = context.globalFlags.Lookup(name).Value.Get().(bool) + } + + return +} + +// DependencyOptions calculates options related to dependecy handling +func (context *AptlyContext) DependencyOptions() int { + context.Lock() + defer context.Unlock() + + if context.dependencyOptions == -1 { + context.dependencyOptions = 0 + if context.lookupOption(context.config().DepFollowSuggests, "dep-follow-suggests") { + context.dependencyOptions |= deb.DepFollowSuggests + } + if context.lookupOption(context.config().DepFollowRecommends, "dep-follow-recommends") { + context.dependencyOptions |= deb.DepFollowRecommends + } + if context.lookupOption(context.config().DepFollowAllVariants, "dep-follow-all-variants") { + context.dependencyOptions |= deb.DepFollowAllVariants + } + if context.lookupOption(context.config().DepFollowSource, "dep-follow-source") { + context.dependencyOptions |= deb.DepFollowSource + } + } + + return context.dependencyOptions +} + +// ArchitecturesList returns list of architectures fixed via command line or config +func (context *AptlyContext) ArchitecturesList() []string { + context.Lock() + defer context.Unlock() + + if context.architecturesList == nil { + context.architecturesList = context.config().Architectures + optionArchitectures := context.globalFlags.Lookup("architectures").Value.String() + if optionArchitectures != "" { + context.architecturesList = strings.Split(optionArchitectures, ",") + } + } + + return context.architecturesList +} + +// Progress creates or returns Progress object +func (context *AptlyContext) Progress() aptly.Progress { + context.Lock() + defer context.Unlock() + + return context._progress() +} + +func (context *AptlyContext) _progress() aptly.Progress { + if context.progress == nil { + context.progress = console.NewProgress() + context.progress.Start() + } + + return context.progress +} + +// Downloader returns instance of current downloader +func (context *AptlyContext) Downloader() aptly.Downloader { + context.Lock() + defer context.Unlock() + + if context.downloader == nil { + var downloadLimit int64 + limitFlag := context.flags.Lookup("download-limit") + if limitFlag != nil { + downloadLimit = limitFlag.Value.Get().(int64) + } + if downloadLimit == 0 { + downloadLimit = context.config().DownloadLimit + } + context.downloader = http.NewDownloader(context.config().DownloadConcurrency, + downloadLimit*1024, context._progress()) + } + + return context.downloader +} + +// DBPath builds path to database +func (context *AptlyContext) DBPath() string { + context.Lock() + defer context.Unlock() + + return context.dbPath() +} + +// DBPath builds path to database +func (context *AptlyContext) dbPath() string { + return filepath.Join(context.config().RootDir, "db") +} + +// Database opens and returns current instance of database +func (context *AptlyContext) Database() (database.Storage, error) { + context.Lock() + defer context.Unlock() + + return context._database() +} + +func (context *AptlyContext) _database() (database.Storage, error) { + if context.database == nil { + var err error + + context.database, err = database.OpenDB(context.dbPath()) + if err != nil { + return nil, fmt.Errorf("can't open database: %s", err) + } + } + + return context.database, nil +} + +// CloseDatabase closes the db temporarily +func (context *AptlyContext) CloseDatabase() error { + context.Lock() + defer context.Unlock() + + if context.database == nil { + return nil + } + + return context.database.Close() +} + +// ReOpenDatabase reopens the db after close +func (context *AptlyContext) ReOpenDatabase() error { + context.Lock() + defer context.Unlock() + + if context.database == nil { + return nil + } + + const MaxTries = 10 + const Delay = 10 * time.Second + + for try := 0; try < MaxTries; try++ { + err := context.database.ReOpen() + if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 { + return err + } + context._progress().Printf("Unable to reopen database, sleeping %s\n", Delay) + <-time.After(Delay) + } + + return fmt.Errorf("unable to reopen the DB, maximum number of retries reached") +} + +// CollectionFactory builds factory producing all kinds of collections +func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory { + context.Lock() + defer context.Unlock() + + if context.collectionFactory == nil { + db, err := context._database() + if err != nil { + Fatal(err) + } + context.collectionFactory = deb.NewCollectionFactory(db) + } + + return context.collectionFactory +} + +// PackagePool returns instance of PackagePool +func (context *AptlyContext) PackagePool() aptly.PackagePool { + context.Lock() + defer context.Unlock() + + if context.packagePool == nil { + context.packagePool = files.NewPackagePool(context.config().RootDir) + } + + return context.packagePool +} + +// GetPublishedStorage returns instance of PublishedStorage +func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage { + context.Lock() + defer context.Unlock() + + publishedStorage, ok := context.publishedStorages[name] + if !ok { + if name == "" { + publishedStorage = files.NewPublishedStorage(context.config().RootDir) + } else if strings.HasPrefix(name, "s3:") { + params, ok := context.config().S3PublishRoots[name[3:]] + if !ok { + Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:])) + } + + var err error + publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey, + params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass, + params.EncryptionMethod, params.PlusWorkaround) + if err != nil { + Fatal(err) + } + } else if strings.HasPrefix(name, "swift:") { + params, ok := context.config().SwiftPublishRoots[name[6:]] + if !ok { + Fatal(fmt.Errorf("published Swift storage %v not configured", name[6:])) + } + + var err error + publishedStorage, err = swift.NewPublishedStorage(params.UserName, params.Password, + params.AuthURL, params.Tenant, params.TenantID, params.Container, params.Prefix) + if err != nil { + Fatal(err) + } + } else { + Fatal(fmt.Errorf("unknown published storage format: %v", name)) + } + context.publishedStorages[name] = publishedStorage + } + + return publishedStorage +} + +// UploadPath builds path to upload storage +func (context *AptlyContext) UploadPath() string { + return filepath.Join(context.Config().RootDir, "upload") +} + +// UpdateFlags sets internal copy of flags in the context +func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) { + context.Lock() + defer context.Unlock() + + context.flags = flags +} + +// Flags returns current command flags +func (context *AptlyContext) Flags() *flag.FlagSet { + context.Lock() + defer context.Unlock() + + return context.flags +} + +// GlobalFlags returns flags passed to all commands +func (context *AptlyContext) GlobalFlags() *flag.FlagSet { + context.Lock() + defer context.Unlock() + + return context.globalFlags +} + +// Shutdown shuts context down +func (context *AptlyContext) Shutdown() { + context.Lock() + defer context.Unlock() + + if aptly.EnableDebug { + if context.fileMemProfile != nil { + pprof.WriteHeapProfile(context.fileMemProfile) + context.fileMemProfile.Close() + context.fileMemProfile = nil + } + if context.fileCPUProfile != nil { + pprof.StopCPUProfile() + context.fileCPUProfile.Close() + context.fileCPUProfile = nil + } + if context.fileMemProfile != nil { + context.fileMemProfile.Close() + context.fileMemProfile = nil + } + } + if context.database != nil { + context.database.Close() + context.database = nil + } + if context.downloader != nil { + context.downloader.Abort() + context.downloader = nil + } + if context.progress != nil { + context.progress.Shutdown() + context.progress = nil + } +} + +// Cleanup does partial shutdown of context +func (context *AptlyContext) Cleanup() { + context.Lock() + defer context.Unlock() + + if context.downloader != nil { + context.downloader.Shutdown() + context.downloader = nil + } + if context.progress != nil { + context.progress.Shutdown() + context.progress = nil + } +} + +// NewContext initializes context with default settings +func NewContext(flags *flag.FlagSet) (*AptlyContext, error) { + var err error + + context := &AptlyContext{ + flags: flags, + globalFlags: flags, + dependencyOptions: -1, + publishedStorages: map[string]aptly.PublishedStorage{}, + } + + if aptly.EnableDebug { + cpuprofile := flags.Lookup("cpuprofile").Value.String() + if cpuprofile != "" { + context.fileCPUProfile, err = os.Create(cpuprofile) + if err != nil { + return nil, err + } + pprof.StartCPUProfile(context.fileCPUProfile) + } + + memprofile := flags.Lookup("memprofile").Value.String() + if memprofile != "" { + context.fileMemProfile, err = os.Create(memprofile) + if err != nil { + return nil, err + } + } + + memstats := flags.Lookup("memstats").Value.String() + if memstats != "" { + interval := flags.Lookup("meminterval").Value.Get().(time.Duration) + + context.fileMemStats, err = os.Create(memstats) + if err != nil { + return nil, err + } + + context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n") + + go func() { + var stats runtime.MemStats + + start := time.Now().UnixNano() + + for { + runtime.ReadMemStats(&stats) + if context.fileMemStats != nil { + context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n", + (time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased)) + time.Sleep(interval) + } else { + break + } + } + }() + } + } + + return context, nil +} diff --git a/src/github.com/smira/aptly/database/leveldb.go b/src/github.com/smira/aptly/database/leveldb.go index 7876b38e..2ad2ff86 100644 --- a/src/github.com/smira/aptly/database/leveldb.go +++ b/src/github.com/smira/aptly/database/leveldb.go @@ -101,7 +101,7 @@ func (l *levelDB) Put(key []byte, value []byte) error { return err } } else { - if bytes.Compare(old, value) == 0 { + if bytes.Equal(old, value) { return nil } } diff --git a/src/github.com/smira/aptly/database/leveldb_test.go b/src/github.com/smira/aptly/database/leveldb_test.go index e2624723..2119c388 100644 --- a/src/github.com/smira/aptly/database/leveldb_test.go +++ b/src/github.com/smira/aptly/database/leveldb_test.go @@ -1,8 +1,9 @@ package database import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/deb/collections.go b/src/github.com/smira/aptly/deb/collections.go index dd5fd9d5..a34363b4 100644 --- a/src/github.com/smira/aptly/deb/collections.go +++ b/src/github.com/smira/aptly/deb/collections.go @@ -2,10 +2,12 @@ package deb import ( "github.com/smira/aptly/database" + "sync" ) // CollectionFactory is a single place to generate all desired collections type CollectionFactory struct { + *sync.Mutex db database.Storage packages *PackageCollection remoteRepos *RemoteRepoCollection @@ -16,11 +18,14 @@ type CollectionFactory struct { // NewCollectionFactory creates new factory func NewCollectionFactory(db database.Storage) *CollectionFactory { - return &CollectionFactory{db: db} + return &CollectionFactory{Mutex: &sync.Mutex{}, db: db} } // PackageCollection returns (or creates) new PackageCollection func (factory *CollectionFactory) PackageCollection() *PackageCollection { + factory.Lock() + defer factory.Unlock() + if factory.packages == nil { factory.packages = NewPackageCollection(factory.db) } @@ -30,6 +35,9 @@ func (factory *CollectionFactory) PackageCollection() *PackageCollection { // RemoteRepoCollection returns (or creates) new RemoteRepoCollection func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection { + factory.Lock() + defer factory.Unlock() + if factory.remoteRepos == nil { factory.remoteRepos = NewRemoteRepoCollection(factory.db) } @@ -39,6 +47,9 @@ func (factory *CollectionFactory) RemoteRepoCollection() *RemoteRepoCollection { // SnapshotCollection returns (or creates) new SnapshotCollection func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection { + factory.Lock() + defer factory.Unlock() + if factory.snapshots == nil { factory.snapshots = NewSnapshotCollection(factory.db) } @@ -48,6 +59,9 @@ func (factory *CollectionFactory) SnapshotCollection() *SnapshotCollection { // LocalRepoCollection returns (or creates) new LocalRepoCollection func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection { + factory.Lock() + defer factory.Unlock() + if factory.localRepos == nil { factory.localRepos = NewLocalRepoCollection(factory.db) } @@ -57,9 +71,24 @@ func (factory *CollectionFactory) LocalRepoCollection() *LocalRepoCollection { // PublishedRepoCollection returns (or creates) new PublishedRepoCollection func (factory *CollectionFactory) PublishedRepoCollection() *PublishedRepoCollection { + factory.Lock() + defer factory.Unlock() + if factory.publishedRepos == nil { factory.publishedRepos = NewPublishedRepoCollection(factory.db) } return factory.publishedRepos } + +// Flush removes all references to collections, so that memory could be reclaimed +func (factory *CollectionFactory) Flush() { + factory.Lock() + defer factory.Unlock() + + factory.localRepos = nil + factory.snapshots = nil + factory.remoteRepos = nil + factory.publishedRepos = nil + factory.packages = nil +} diff --git a/src/github.com/smira/aptly/deb/deb.go b/src/github.com/smira/aptly/deb/deb.go index 6d749f3c..c4985558 100644 --- a/src/github.com/smira/aptly/deb/deb.go +++ b/src/github.com/smira/aptly/deb/deb.go @@ -47,7 +47,7 @@ func GetControlFileFromDeb(packageFile string) (Stanza, error) { return nil, fmt.Errorf("unable to read .tar archive: %s", err) } - if tarHeader.Name == "./control" { + if tarHeader.Name == "./control" || tarHeader.Name == "control" { reader := NewControlFileReader(untar) stanza, err := reader.ReadStanza() if err != nil { diff --git a/src/github.com/smira/aptly/deb/deb_test.go b/src/github.com/smira/aptly/deb/deb_test.go index d2f720e0..4c329c04 100644 --- a/src/github.com/smira/aptly/deb/deb_test.go +++ b/src/github.com/smira/aptly/deb/deb_test.go @@ -2,9 +2,10 @@ package deb import ( "github.com/smira/aptly/utils" - . "launchpad.net/gocheck" "path/filepath" "runtime" + + . "gopkg.in/check.v1" ) type DebSuite struct { diff --git a/src/github.com/smira/aptly/deb/debian_test.go b/src/github.com/smira/aptly/deb/debian_test.go index 8406e198..e0cc4870 100644 --- a/src/github.com/smira/aptly/deb/debian_test.go +++ b/src/github.com/smira/aptly/deb/debian_test.go @@ -1,8 +1,9 @@ package deb import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/deb/format.go b/src/github.com/smira/aptly/deb/format.go index 4e8d4abc..5663a564 100644 --- a/src/github.com/smira/aptly/deb/format.go +++ b/src/github.com/smira/aptly/deb/format.go @@ -11,9 +11,76 @@ import ( type Stanza map[string]string // Canonical order of fields in stanza -var canocialOrder = []string{"Package", "Origin", "Label", "Suite", "Version", "Installed-Size", "Priority", "Section", "Maintainer", - "Architecture", "Codename", "Date", "Architectures", "Components", "Description", "MD5sum", "MD5Sum", "SHA1", "SHA256", - "Archive", "Component"} +// Taken from: http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/vivid/apt/vivid/view/head:/apt-pkg/tagfile.cc#L504 +var ( + canonicalOrderRelease = []string{ + "Origin", + "Label", + "Archive", + "Suite", + "Version", + "Codename", + "Date", + "Architectures", + "Architecture", + "Components", + "Component", + "Description", + "MD5Sum", + "SHA1", + "SHA256", + } + + canonicalOrderBinary = []string{ + "Package", + "Essential", + "Status", + "Priority", + "Section", + "Installed-Size", + "Maintainer", + "Original-Maintainer", + "Architecture", + "Source", + "Version", + "Replaces", + "Provides", + "Depends", + "Pre-Depends", + "Recommends", + "Suggests", + "Conflicts", + "Breaks", + "Conffiles", + "Filename", + "Size", + "MD5Sum", + "MD5sum", + "SHA1", + "SHA256", + "Description", + } + + canonicalOrderSource = []string{ + "Package", + "Source", + "Binary", + "Version", + "Priority", + "Section", + "Maintainer", + "Original-Maintainer", + "Build-Depends", + "Build-Depends-Indep", + "Build-Conflicts", + "Build-Conflicts-Indep", + "Architecture", + "Standards-Version", + "Format", + "Directory", + "Files", + } +) // Copy returns copy of Stanza func (s Stanza) Copy() (result Stanza) { @@ -41,8 +108,16 @@ func writeField(w *bufio.Writer, field, value string) (err error) { } // WriteTo saves stanza back to stream, modifying itself on the fly -func (s Stanza) WriteTo(w *bufio.Writer) error { - for _, field := range canocialOrder { +func (s Stanza) WriteTo(w *bufio.Writer, isSource, isRelease bool) error { + canonicalOrder := canonicalOrderBinary + if isSource { + canonicalOrder = canonicalOrderSource + } + if isRelease { + canonicalOrder = canonicalOrderRelease + } + + for _, field := range canonicalOrder { value, ok := s[field] if ok { delete(s, field) diff --git a/src/github.com/smira/aptly/deb/format_test.go b/src/github.com/smira/aptly/deb/format_test.go index b7e843c3..41046def 100644 --- a/src/github.com/smira/aptly/deb/format_test.go +++ b/src/github.com/smira/aptly/deb/format_test.go @@ -3,8 +3,9 @@ package deb import ( "bufio" "bytes" - . "launchpad.net/gocheck" "strings" + + . "gopkg.in/check.v1" ) type ControlFileSuite struct { @@ -107,7 +108,7 @@ func (s *ControlFileSuite) TestReadWriteStanza(c *C) { buf := &bytes.Buffer{} w := bufio.NewWriter(buf) - err = stanza.Copy().WriteTo(w) + err = stanza.Copy().WriteTo(w, false, false) c.Assert(err, IsNil) err = w.Flush() c.Assert(err, IsNil) diff --git a/src/github.com/smira/aptly/deb/graph.go b/src/github.com/smira/aptly/deb/graph.go new file mode 100644 index 00000000..fe75862b --- /dev/null +++ b/src/github.com/smira/aptly/deb/graph.go @@ -0,0 +1,120 @@ +package deb + +import ( + "code.google.com/p/gographviz" + "fmt" + "strings" +) + +// BuildGraph generates graph contents from aptly object database +func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, error) { + var err error + + graph := gographviz.NewEscape() + graph.SetDir(true) + graph.SetName("aptly") + + existingNodes := map[string]bool{} + + err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error { + err := collectionFactory.RemoteRepoCollection().LoadComplete(repo) + if err != nil { + return err + } + + graph.AddNode("aptly", repo.UUID, map[string]string{ + "shape": "Mrecord", + "style": "filled", + "fillcolor": "darkgoldenrod1", + "label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}", + repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "), + strings.Join(repo.Architectures, ", "), repo.NumPackages()), + }) + existingNodes[repo.UUID] = true + return nil + }) + + if err != nil { + return nil, err + } + + err = collectionFactory.LocalRepoCollection().ForEach(func(repo *LocalRepo) error { + err := collectionFactory.LocalRepoCollection().LoadComplete(repo) + if err != nil { + return err + } + + graph.AddNode("aptly", repo.UUID, map[string]string{ + "shape": "Mrecord", + "style": "filled", + "fillcolor": "mediumseagreen", + "label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}", + repo.Name, repo.Comment, repo.NumPackages()), + }) + existingNodes[repo.UUID] = true + return nil + }) + + if err != nil { + return nil, err + } + + collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error { + existingNodes[snapshot.UUID] = true + return nil + }) + + err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error { + err := collectionFactory.SnapshotCollection().LoadComplete(snapshot) + if err != nil { + return err + } + + description := snapshot.Description + if snapshot.SourceKind == "repo" { + description = "Snapshot from repo" + } + + graph.AddNode("aptly", snapshot.UUID, map[string]string{ + "shape": "Mrecord", + "style": "filled", + "fillcolor": "cadetblue1", + "label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()), + }) + + if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" { + for _, uuid := range snapshot.SourceIDs { + _, exists := existingNodes[uuid] + if exists { + graph.AddEdge(uuid, snapshot.UUID, true, nil) + } + } + } + return nil + }) + + if err != nil { + return nil, err + } + + collectionFactory.PublishedRepoCollection().ForEach(func(repo *PublishedRepo) error { + graph.AddNode("aptly", repo.UUID, map[string]string{ + "shape": "Mrecord", + "style": "filled", + "fillcolor": "darkolivegreen1", + "label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution, + strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")), + }) + + for _, uuid := range repo.Sources { + _, exists := existingNodes[uuid] + if exists { + graph.AddEdge(uuid, repo.UUID, true, nil) + } + } + + return nil + }) + + return graph, nil +} diff --git a/src/github.com/smira/aptly/deb/import.go b/src/github.com/smira/aptly/deb/import.go new file mode 100644 index 00000000..ceea8ff9 --- /dev/null +++ b/src/github.com/smira/aptly/deb/import.go @@ -0,0 +1,163 @@ +package deb + +import ( + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/utils" + "os" + "path/filepath" + "sort" + "strings" +) + +// CollectPackageFiles walks filesystem collecting all candidates for package files +func CollectPackageFiles(locations []string, reporter aptly.ResultReporter) (packageFiles, failedFiles []string, err error) { + for _, location := range locations { + info, err2 := os.Stat(location) + if err2 != nil { + reporter.Warning("Unable to process %s: %s", location, err2) + failedFiles = append(failedFiles, location) + continue + } + if info.IsDir() { + err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error { + if err3 != nil { + return err3 + } + if info.IsDir() { + return nil + } + + if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || + strings.HasSuffix(info.Name(), ".dsc") { + packageFiles = append(packageFiles, path) + } + + return nil + }) + } else { + if strings.HasSuffix(info.Name(), ".deb") || strings.HasSuffix(info.Name(), ".udeb") || + strings.HasSuffix(info.Name(), ".dsc") { + packageFiles = append(packageFiles, location) + } else { + reporter.Warning("Unknown file extension: %s", location) + failedFiles = append(failedFiles, location) + continue + } + } + } + + sort.Strings(packageFiles) + + return +} + +// ImportPackageFiles imports files into local repository +func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace bool, verifier utils.Verifier, + pool aptly.PackagePool, collection *PackageCollection, reporter aptly.ResultReporter) (processedFiles []string, failedFiles []string, err error) { + if forceReplace { + list.PrepareIndex() + } + + for _, file := range packageFiles { + var ( + stanza Stanza + p *Package + ) + + candidateProcessedFiles := []string{} + isSourcePackage := strings.HasSuffix(file, ".dsc") + isUdebPackage := strings.HasSuffix(file, ".udeb") + + if isSourcePackage { + stanza, err = GetControlFileFromDsc(file, verifier) + + if err == nil { + stanza["Package"] = stanza["Source"] + delete(stanza, "Source") + + p, err = NewSourcePackageFromControlFile(stanza) + } + } else { + stanza, err = GetControlFileFromDeb(file) + if isUdebPackage { + p = NewUdebPackageFromControlFile(stanza) + } else { + p = NewPackageFromControlFile(stanza) + } + } + if err != nil { + reporter.Warning("Unable to read file %s: %s", file, err) + failedFiles = append(failedFiles, file) + continue + } + + var checksums utils.ChecksumInfo + checksums, err = utils.ChecksumsForFile(file) + if err != nil { + return nil, nil, err + } + + if isSourcePackage { + p.UpdateFiles(append(p.Files(), PackageFile{Filename: filepath.Base(file), Checksums: checksums})) + } else { + p.UpdateFiles([]PackageFile{{Filename: filepath.Base(file), Checksums: checksums}}) + } + + err = pool.Import(file, checksums.MD5) + if err != nil { + reporter.Warning("Unable to import file %s into pool: %s", file, err) + failedFiles = append(failedFiles, file) + continue + } + + candidateProcessedFiles = append(candidateProcessedFiles, file) + + // go over all files, except for the last one (.dsc/.deb itself) + for _, f := range p.Files() { + if filepath.Base(f.Filename) == filepath.Base(file) { + continue + } + sourceFile := filepath.Join(filepath.Dir(file), filepath.Base(f.Filename)) + err = pool.Import(sourceFile, f.Checksums.MD5) + if err != nil { + reporter.Warning("Unable to import file %s into pool: %s", sourceFile, err) + failedFiles = append(failedFiles, file) + break + } + + candidateProcessedFiles = append(candidateProcessedFiles, sourceFile) + } + if err != nil { + // some files haven't been imported + continue + } + + err = collection.Update(p) + if err != nil { + reporter.Warning("Unable to save package %s: %s", p, err) + failedFiles = append(failedFiles, file) + continue + } + + if forceReplace { + conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true) + for _, cp := range conflictingPackages { + reporter.Removed("%s removed due to conflict with package being added", cp) + list.Remove(cp) + } + } + + err = list.Add(p) + if err != nil { + reporter.Warning("Unable to add package to repo %s: %s", p, err) + failedFiles = append(failedFiles, file) + continue + } + + reporter.Added("%s added", p) + processedFiles = append(processedFiles, candidateProcessedFiles...) + } + + err = nil + return +} diff --git a/src/github.com/smira/aptly/deb/index_files.go b/src/github.com/smira/aptly/deb/index_files.go index a1eb0015..962c988b 100644 --- a/src/github.com/smira/aptly/deb/index_files.go +++ b/src/github.com/smira/aptly/deb/index_files.go @@ -150,7 +150,10 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s } func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexFile { - key := fmt.Sprintf("pi-%s-%s-%s", component, arch, udeb) + if arch == "source" { + udeb = false + } + key := fmt.Sprintf("pi-%s-%s-%v", component, arch, udeb) file, ok := files.indexes[key] if !ok { var relativePath string @@ -180,7 +183,10 @@ func (files *indexFiles) PackageIndex(component, arch string, udeb bool) *indexF } func (files *indexFiles) ReleaseIndex(component, arch string, udeb bool) *indexFile { - key := fmt.Sprintf("ri-%s-%s-%s", component, arch, udeb) + if arch == "source" { + udeb = false + } + key := fmt.Sprintf("ri-%s-%s-%v", component, arch, udeb) file, ok := files.indexes[key] if !ok { var relativePath string diff --git a/src/github.com/smira/aptly/deb/list.go b/src/github.com/smira/aptly/deb/list.go index 3e78234b..dc59d232 100644 --- a/src/github.com/smira/aptly/deb/list.go +++ b/src/github.com/smira/aptly/deb/list.go @@ -38,6 +38,11 @@ type PackageList struct { providesIndex map[string][]*Package } +// PackageConflictError means that package can't be added to the list due to error +type PackageConflictError struct { + error +} + // Verify interface var ( _ sort.Interface = &PackageList{} @@ -90,7 +95,7 @@ func (l *PackageList) Add(p *Package) error { existing, ok := l.packages[key] if ok { if !existing.Equals(p) { - return fmt.Errorf("conflict in package %s", p) + return &PackageConflictError{fmt.Errorf("conflict in package %s", p)} } return nil } @@ -205,6 +210,19 @@ func (l *PackageList) Architectures(includeSource bool) (result []string) { return } +// Strings builds list of strings with package keys +func (l *PackageList) Strings() []string { + result := make([]string, l.Len()) + i := 0 + + for _, p := range l.packages { + result[i] = string(p.Key("")) + i += 1 + } + + return result +} + // depSliceDeduplicate removes dups in slice of Dependencies func depSliceDeduplicate(s []Dependency) []Dependency { l := len(s) diff --git a/src/github.com/smira/aptly/deb/list_test.go b/src/github.com/smira/aptly/deb/list_test.go index 547a2fb3..c9075401 100644 --- a/src/github.com/smira/aptly/deb/list_test.go +++ b/src/github.com/smira/aptly/deb/list_test.go @@ -2,10 +2,11 @@ package deb import ( "errors" - . "launchpad.net/gocheck" "regexp" "sort" "strings" + + . "gopkg.in/check.v1" ) type containsChecker struct { @@ -79,20 +80,20 @@ func (s *PackageListSuite) SetUpTest(c *C) { s.il = NewPackageList() s.packages = []*Package{ - &Package{Name: "lib", Version: "1.0", Architecture: "i386", Source: "lib (0.9)", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}}, - &Package{Name: "dpkg", Version: "1.7", Architecture: "i386", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, - &Package{Name: "data", Version: "1.1~bp1", Architecture: "all", Source: "app", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, - &Package{Name: "app", Version: "1.1~bp1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, - &Package{Name: "mailer", Version: "3.5.8", Architecture: "i386", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, - &Package{Name: "app", Version: "1.1~bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, - &Package{Name: "app", Version: "1.1~bp1", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, - &Package{Name: "app", Version: "1.0", Architecture: "s390", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, - &Package{Name: "aa", Version: "2.0-1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, - &Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, - &Package{Name: "libx", Version: "1.5", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, - &Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "arm", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, - &Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, - &Package{Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "lib", Version: "1.0", Architecture: "i386", Source: "lib (0.9)", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}}, + {Name: "dpkg", Version: "1.7", Architecture: "i386", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, + {Name: "data", Version: "1.1~bp1", Architecture: "all", Source: "app", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, + {Name: "app", Version: "1.1~bp1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "mailer", Version: "3.5.8", Architecture: "i386", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, + {Name: "app", Version: "1.1~bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "app", Version: "1.1~bp1", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, + {Name: "app", Version: "1.0", Architecture: "s390", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "aa", Version: "2.0-1", Architecture: "i386", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, + {Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, + {Name: "libx", Version: "1.5", Architecture: "arm", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}}}, + {Name: "dpkg", Version: "1.6.1-3", Architecture: "arm", Provides: []string{"package-installer"}, deps: &PackageDependencies{}}, + {Name: "dpkg", Version: "1.6.1-3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "dpkg", Version: "1.7", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, } for _, p := range s.packages { s.il.Add(p) @@ -101,12 +102,12 @@ func (s *PackageListSuite) SetUpTest(c *C) { s.il2 = NewPackageList() s.packages2 = []*Package{ - &Package{Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, - &Package{Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, - &Package{Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, - &Package{Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, - &Package{Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, - &Package{Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "mailer", Version: "3.5.8", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, + {Name: "sendmail", Version: "1.0", Architecture: "amd64", Source: "postfix (1.3)", Provides: []string{"mail-agent"}, deps: &PackageDependencies{}}, + {Name: "app", Version: "1.1-bp1", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "app", Version: "1.1-bp2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, + {Name: "app", Version: "1.2", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9) | libx (>= 1.5)", "data (>= 1.0) | mail-agent"}}}, + {Name: "app", Version: "3.0", Architecture: "amd64", deps: &PackageDependencies{PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}}, } for _, p := range s.packages2 { s.il2.Add(p) @@ -114,10 +115,10 @@ func (s *PackageListSuite) SetUpTest(c *C) { s.il2.PrepareIndex() s.sourcePackages = []*Package{ - &Package{Name: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, - &Package{Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, - &Package{Name: "aa", Version: "2.0-1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, - &Package{Name: "lib", Version: "0.9", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "postfix", Version: "1.3", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "app", Version: "1.1~bp1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "aa", Version: "2.0-1", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, + {Name: "lib", Version: "0.9", Architecture: "source", SourceArchitecture: "any", IsSource: true, deps: &PackageDependencies{}}, } } @@ -387,7 +388,7 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) { missing, err = s.il.VerifyDependencies(0, []string{"i386", "amd64"}, s.il, nil) c.Check(err, IsNil) - c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) + c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) missing, err = s.il.VerifyDependencies(0, []string{"arm"}, s.il, nil) c.Check(err, IsNil) @@ -395,8 +396,8 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) { missing, err = s.il.VerifyDependencies(DepFollowAllVariants, []string{"arm"}, s.il, nil) c.Check(err, IsNil) - c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"}, - Dependency{Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}}) + c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "arm"}, + {Pkg: "mail-agent", Relation: VersionDontCare, Version: "", Architecture: "arm"}}) for _, p := range s.sourcePackages { s.il.Add(p) @@ -404,11 +405,11 @@ func (s *PackageListSuite) TestVerifyDependencies(c *C) { missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"i386", "amd64"}, s.il, nil) c.Check(err, IsNil) - c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) + c.Check(missing, DeepEquals, []Dependency{{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) missing, err = s.il.VerifyDependencies(DepFollowSource, []string{"arm"}, s.il, nil) c.Check(err, IsNil) - c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "libx", Relation: VersionEqual, Version: "1.5", Architecture: "source"}}) + c.Check(missing, DeepEquals, []Dependency{{Pkg: "libx", Relation: VersionEqual, Version: "1.5", Architecture: "source"}}) _, err = s.il.VerifyDependencies(0, []string{"i386", "amd64", "s390"}, s.il, nil) c.Check(err, ErrorMatches, "unable to process package app_1.0_s390:.*") diff --git a/src/github.com/smira/aptly/deb/local.go b/src/github.com/smira/aptly/deb/local.go index 4002b546..f57e4dde 100644 --- a/src/github.com/smira/aptly/deb/local.go +++ b/src/github.com/smira/aptly/deb/local.go @@ -7,12 +7,13 @@ import ( "github.com/smira/aptly/database" "github.com/ugorji/go/codec" "log" + "sync" ) // LocalRepo is a collection of packages created locally type LocalRepo struct { // Permanent internal ID - UUID string + UUID string `json:"-"` // User-assigned name Name string // Comment @@ -88,6 +89,7 @@ func (repo *LocalRepo) RefKey() []byte { // LocalRepoCollection does listing, updating/adding/deleting of LocalRepos type LocalRepoCollection struct { + *sync.RWMutex db database.Storage list []*LocalRepo } @@ -95,7 +97,8 @@ type LocalRepoCollection struct { // NewLocalRepoCollection loads LocalRepos from DB and makes up collection func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection { result := &LocalRepoCollection{ - db: db, + RWMutex: &sync.RWMutex{}, + db: db, } blobs := db.FetchByPrefix([]byte("L")) @@ -104,7 +107,7 @@ func NewLocalRepoCollection(db database.Storage) *LocalRepoCollection { for _, blob := range blobs { r := &LocalRepo{} if err := r.Decode(blob); err != nil { - log.Printf("Error decoding mirror: %s\n", err) + log.Printf("Error decoding repo: %s\n", err) } else { result.list = append(result.list, r) } diff --git a/src/github.com/smira/aptly/deb/local_test.go b/src/github.com/smira/aptly/deb/local_test.go index 0830c1ef..ebcce105 100644 --- a/src/github.com/smira/aptly/deb/local_test.go +++ b/src/github.com/smira/aptly/deb/local_test.go @@ -3,7 +3,8 @@ package deb import ( "errors" "github.com/smira/aptly/database" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type LocalRepoSuite struct { diff --git a/src/github.com/smira/aptly/deb/package.go b/src/github.com/smira/aptly/deb/package.go index be734ed5..f032a222 100644 --- a/src/github.com/smira/aptly/deb/package.go +++ b/src/github.com/smira/aptly/deb/package.go @@ -1,6 +1,7 @@ package deb import ( + "encoding/json" "fmt" "github.com/smira/aptly/aptly" "github.com/smira/aptly/utils" @@ -38,6 +39,11 @@ type Package struct { collection *PackageCollection } +// Check interface +var ( + _ json.Marshaler = &Package{} +) + // NewPackageFromControlFile creates Package from parsed Debian control file func NewPackageFromControlFile(input Stanza) *Package { result := &Package{ @@ -55,12 +61,18 @@ func NewPackageFromControlFile(input Stanza) *Package { filesize, _ := strconv.ParseInt(input["Size"], 10, 64) + md5, ok := input["MD5sum"] + if !ok { + // there are some broken repos out there with MD5 in wrong field + md5 = input["MD5Sum"] + } + result.UpdateFiles(PackageFiles{PackageFile{ Filename: filepath.Base(input["Filename"]), downloadPath: filepath.Dir(input["Filename"]), Checksums: utils.ChecksumInfo{ Size: filesize, - MD5: strings.TrimSpace(input["MD5sum"]), + MD5: strings.TrimSpace(md5), SHA1: strings.TrimSpace(input["SHA1"]), SHA256: strings.TrimSpace(input["SHA256"]), }, @@ -68,6 +80,7 @@ func NewPackageFromControlFile(input Stanza) *Package { delete(input, "Filename") delete(input, "MD5sum") + delete(input, "MD5Sum") delete(input, "SHA1") delete(input, "SHA256") delete(input, "Size") @@ -198,6 +211,16 @@ func (p *Package) String() string { return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture) } +// MarshalJSON implements json.Marshaller interface +func (p *Package) MarshalJSON() ([]byte, error) { + stanza := p.Stanza() + stanza["FilesHash"] = fmt.Sprintf("%08x", p.FilesHash) + stanza["Key"] = string(p.Key("")) + stanza["ShortKey"] = string(p.ShortKey("")) + + return json.Marshal(stanza) +} + // GetField returns fields from package func (p *Package) GetField(name string) string { switch name { @@ -262,7 +285,6 @@ func (p *Package) GetField(name string) string { default: return p.Extra()[name] } - return "" } // MatchesArchitecture checks whether packages matches specified architecture @@ -404,7 +426,9 @@ func (p *Package) Stanza() (result Stanza) { result["Architecture"] = p.SourceArchitecture } else { result["Architecture"] = p.Architecture - result["Source"] = p.Source + if p.Source != "" { + result["Source"] = p.Source + } } if p.IsSource { diff --git a/src/github.com/smira/aptly/deb/package_collection_test.go b/src/github.com/smira/aptly/deb/package_collection_test.go index 4f1dcc5c..6ca363d9 100644 --- a/src/github.com/smira/aptly/deb/package_collection_test.go +++ b/src/github.com/smira/aptly/deb/package_collection_test.go @@ -3,7 +3,8 @@ package deb import ( "github.com/smira/aptly/database" "github.com/smira/aptly/utils" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type PackageCollectionSuite struct { diff --git a/src/github.com/smira/aptly/deb/package_files_test.go b/src/github.com/smira/aptly/deb/package_files_test.go index 6b2adb39..fe11193f 100644 --- a/src/github.com/smira/aptly/deb/package_files_test.go +++ b/src/github.com/smira/aptly/deb/package_files_test.go @@ -3,9 +3,10 @@ package deb import ( "github.com/smira/aptly/files" "github.com/smira/aptly/utils" - . "launchpad.net/gocheck" "os" "path/filepath" + + . "gopkg.in/check.v1" ) type PackageFilesSuite struct { diff --git a/src/github.com/smira/aptly/deb/package_test.go b/src/github.com/smira/aptly/deb/package_test.go index ddcf754e..c1dc9829 100644 --- a/src/github.com/smira/aptly/deb/package_test.go +++ b/src/github.com/smira/aptly/deb/package_test.go @@ -4,10 +4,11 @@ import ( "bytes" "github.com/smira/aptly/files" "github.com/smira/aptly/utils" - . "launchpad.net/gocheck" "os" "path/filepath" "regexp" + + . "gopkg.in/check.v1" ) type PackageSuite struct { @@ -398,7 +399,7 @@ func (s *PackageSuite) TestDownloadList(c *C) { list, err := p.DownloadList(packagePool) c.Check(err, IsNil) c.Check(list, DeepEquals, []PackageDownloadTask{ - PackageDownloadTask{ + { RepoURI: "pool/contrib/a/alien-arena/alien-arena-common_7.40-2_i386.deb", DestinationPath: poolPath, Checksums: utils.ChecksumInfo{Size: 5, diff --git a/src/github.com/smira/aptly/deb/ppa_test.go b/src/github.com/smira/aptly/deb/ppa_test.go index 99467ce7..44bda7c2 100644 --- a/src/github.com/smira/aptly/deb/ppa_test.go +++ b/src/github.com/smira/aptly/deb/ppa_test.go @@ -2,7 +2,8 @@ package deb import ( "github.com/smira/aptly/utils" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type PpaSuite struct { diff --git a/src/github.com/smira/aptly/deb/publish.go b/src/github.com/smira/aptly/deb/publish.go index 7e1c5f9f..cec60d44 100644 --- a/src/github.com/smira/aptly/deb/publish.go +++ b/src/github.com/smira/aptly/deb/publish.go @@ -1,8 +1,10 @@ package deb import ( + "bufio" "bytes" "code.google.com/p/go-uuid/uuid" + "encoding/json" "fmt" "github.com/smira/aptly/aptly" "github.com/smira/aptly/database" @@ -14,6 +16,7 @@ import ( "path/filepath" "sort" "strings" + "sync" "time" ) @@ -56,6 +59,21 @@ type PublishedRepo struct { rePublishing bool } +// ParsePrefix splits [storage:]prefix into components +func ParsePrefix(param string) (storage, prefix string) { + i := strings.LastIndex(param, ":") + if i != -1 { + storage = param[:i] + prefix = param[i+1:] + if prefix == "" { + prefix = "." + } + } else { + prefix = param + } + return +} + // walkUpTree goes from source in the tree of source snapshots/mirrors/local repos // gathering information about declared components and distributions func walkUpTree(source interface{}, collectionFactory *CollectionFactory) (rootDistributions []string, rootComponents []string) { @@ -239,6 +257,40 @@ func NewPublishedRepo(storage, prefix, distribution string, architectures []stri return result, nil } +// MarshalJSON requires object to be "loeaded completely" +func (p *PublishedRepo) MarshalJSON() ([]byte, error) { + type sourceInfo struct { + Component, Name string + } + + sources := []sourceInfo{} + for component, item := range p.sourceItems { + name := "" + if item.snapshot != nil { + name = item.snapshot.Name + } else if item.localRepo != nil { + name = item.localRepo.Name + } else { + panic("no snapshot/local repo") + } + sources = append(sources, sourceInfo{ + Component: component, + Name: name, + }) + } + + return json.Marshal(map[string]interface{}{ + "Architectures": p.Architectures, + "Distribution": p.Distribution, + "Label": p.Label, + "Origin": p.Origin, + "Prefix": p.Prefix, + "SourceKind": p.SourceKind, + "Sources": sources, + "Storage": p.Storage, + }) +} + // String returns human-readable represenation of PublishedRepo func (p *PublishedRepo) String() string { var sources = []string{} @@ -471,7 +523,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP progress.InitBar(int64(list.Len()), false) } - err = list.ForEach(func(pkg *Package) error { + list.PrepareIndex() + + err = list.ForEachIndexed(func(pkg *Package) error { if progress != nil { progress.AddBar(1) } @@ -494,12 +548,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP for _, arch := range p.Architectures { if pkg.MatchesArchitecture(arch) { - bufWriter, err := indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter() + var bufWriter *bufio.Writer + + bufWriter, err = indexes.PackageIndex(component, arch, pkg.IsUdeb).BufWriter() if err != nil { return err } - err = pkg.Stanza().WriteTo(bufWriter) + err = pkg.Stanza().WriteTo(bufWriter, pkg.IsSource, false) if err != nil { return err } @@ -545,9 +601,10 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP release["Origin"] = p.GetOrigin() release["Label"] = p.GetLabel() - bufWriter, err := indexes.ReleaseIndex(component, arch, udeb).BufWriter() + var bufWriter *bufio.Writer + bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter() - err = release.WriteTo(bufWriter) + err = release.WriteTo(bufWriter, false, true) if err != nil { return fmt.Errorf("unable to create Release file: %s", err) } @@ -567,6 +624,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP release := make(Stanza) release["Origin"] = p.GetOrigin() release["Label"] = p.GetLabel() + release["Suite"] = p.Distribution release["Codename"] = p.Distribution release["Date"] = time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST") release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{"source"}), " ") @@ -589,7 +647,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP return err } - err = release.WriteTo(bufWriter) + err = release.WriteTo(bufWriter, false, true) if err != nil { return fmt.Errorf("unable to create Release file: %s", err) } @@ -648,6 +706,7 @@ func (p *PublishedRepo) RemoveFiles(publishedStorageProvider aptly.PublishedStor // PublishedRepoCollection does listing, updating/adding/deleting of PublishedRepos type PublishedRepoCollection struct { + *sync.RWMutex db database.Storage list []*PublishedRepo } @@ -655,7 +714,8 @@ type PublishedRepoCollection struct { // NewPublishedRepoCollection loads PublishedRepos from DB and makes up collection func NewPublishedRepoCollection(db database.Storage) *PublishedRepoCollection { result := &PublishedRepoCollection{ - db: db, + RWMutex: &sync.RWMutex{}, + db: db, } blobs := db.FetchByPrefix([]byte("U")) diff --git a/src/github.com/smira/aptly/deb/publish_test.go b/src/github.com/smira/aptly/deb/publish_test.go index 60e3a226..ab2d8a7d 100644 --- a/src/github.com/smira/aptly/deb/publish_test.go +++ b/src/github.com/smira/aptly/deb/publish_test.go @@ -9,9 +9,10 @@ import ( "github.com/smira/aptly/files" "github.com/ugorji/go/codec" "io/ioutil" - . "launchpad.net/gocheck" "os" "path/filepath" + + . "gopkg.in/check.v1" ) type pathExistsChecker struct { @@ -36,6 +37,9 @@ func (n *NullSigner) Init() error { func (n *NullSigner) SetKey(keyRef string) { } +func (n *NullSigner) SetBatch(batch bool) { +} + func (n *NullSigner) SetKeyRing(keyring, secretKeyring string) { } diff --git a/src/github.com/smira/aptly/deb/reflist.go b/src/github.com/smira/aptly/deb/reflist.go index 7da58773..c70112f9 100644 --- a/src/github.com/smira/aptly/deb/reflist.go +++ b/src/github.com/smira/aptly/deb/reflist.go @@ -2,6 +2,8 @@ package deb import ( "bytes" + "encoding/json" + "github.com/AlekSi/pointer" "github.com/ugorji/go/codec" "sort" ) @@ -92,6 +94,21 @@ func (l *PackageRefList) Has(p *Package) bool { return i < len(l.Refs) && bytes.Compare(l.Refs[i], key) == 0 } +// Strings builds list of strings with package keys +func (l *PackageRefList) Strings() []string { + if l == nil { + return []string{} + } + + result := make([]string, l.Len()) + + for i := 0; i < l.Len(); i++ { + result[i] = string(l.Refs[i]) + } + + return result +} + // Substract returns all packages in l that are not in r func (l *PackageRefList) Substract(r *PackageRefList) *PackageRefList { result := &PackageRefList{Refs: make([][]byte, 0, 128)} @@ -139,6 +156,27 @@ type PackageDiff struct { Left, Right *Package } +// Check interface +var ( + _ json.Marshaler = PackageDiff{} +) + +// MarshalJSON implements json.Marshaler interface +func (d PackageDiff) MarshalJSON() ([]byte, error) { + serialized := struct { + Left, Right *string + }{} + + if d.Left != nil { + serialized.Left = pointer.ToString(string(d.Left.Key(""))) + } + if d.Right != nil { + serialized.Right = pointer.ToString(string(d.Right.Key(""))) + } + + return json.Marshal(serialized) +} + // PackageDiffs is a list of PackageDiff records type PackageDiffs []PackageDiff @@ -232,8 +270,9 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle // Merge merges reflist r into current reflist. If overrideMatching, merge // replaces matching packages (by architecture/name) with reference from r. -// Otherwise, all packages are saved. -func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result *PackageRefList) { +// If ignoreConflicting is set, all packages are preserved, otherwise conflciting +// packages are overwritten with packages from "right" snapshot. +func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConflicting bool) (result *PackageRefList) { var overriddenArch, overridenName []byte // pointer to left and right reflists @@ -270,13 +309,23 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result overridenName = nil overriddenArch = nil } else { + partsL := bytes.Split(rl, []byte(" ")) + archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2] + + partsR := bytes.Split(rr, []byte(" ")) + archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2] + + if !ignoreConflicting && bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) { + // conflicting duplicates with same arch, name, version, but different file hash + result.Refs = append(result.Refs, r.Refs[ir]) + il++ + ir++ + overridenName = nil + overriddenArch = nil + continue + } + if overrideMatching { - partsL := bytes.Split(rl, []byte(" ")) - archL, nameL := partsL[0][1:], partsL[1] - - partsR := bytes.Split(rr, []byte(" ")) - archR, nameR := partsR[0][1:], partsR[1] - if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) { // this package has already been overriden on the right il++ @@ -314,15 +363,15 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching bool) (result // packages and reduces it to only the latest of each package. The operations // are done in-place. This implements a "latest wins" approach which can be used // while merging two or more snapshots together. -func FilterLatestRefs(r *PackageRefList) { +func (l *PackageRefList) FilterLatestRefs() { var ( lastArch, lastName, lastVer []byte arch, name, ver []byte parts [][]byte ) - for i := 0; i < len(r.Refs); i++ { - parts = bytes.Split(r.Refs[i][1:], []byte(" ")) + for i := 0; i < len(l.Refs); i++ { + parts = bytes.Split(l.Refs[i][1:], []byte(" ")) arch, name, ver = parts[0], parts[1], parts[2] if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) { @@ -332,10 +381,10 @@ func FilterLatestRefs(r *PackageRefList) { // Remove the older refs from the result if vres > 0 { // ver[i] > ver[i-1], remove element i-1 - r.Refs = append(r.Refs[:i-1], r.Refs[i:]...) + l.Refs = append(l.Refs[:i-1], l.Refs[i:]...) } else { // ver[i] < ver[i-1], remove element i - r.Refs = append(r.Refs[:i], r.Refs[i+1:]...) + l.Refs = append(l.Refs[:i], l.Refs[i+1:]...) arch, name, ver = lastArch, lastName, lastVer } diff --git a/src/github.com/smira/aptly/deb/reflist_test.go b/src/github.com/smira/aptly/deb/reflist_test.go index c1fdb4ec..eb0aa433 100644 --- a/src/github.com/smira/aptly/deb/reflist_test.go +++ b/src/github.com/smira/aptly/deb/reflist_test.go @@ -3,7 +3,8 @@ package deb import ( "errors" "github.com/smira/aptly/database" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type PackageRefListSuite struct { @@ -168,13 +169,13 @@ func (s *PackageRefListSuite) TestDiff(c *C) { coll := NewPackageCollection(db) packages := []*Package{ - &Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0 - &Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 - &Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 - &Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 - &Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 - &Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 - &Package{Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6 + {Name: "lib", Version: "1.0", Architecture: "i386"}, //0 + {Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 + {Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 + {Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 + {Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 + {Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 + {Name: "xyz", Version: "3.0", Architecture: "sparc"}, //6 } for _, p := range packages { @@ -240,17 +241,20 @@ func (s *PackageRefListSuite) TestMerge(c *C) { coll := NewPackageCollection(db) packages := []*Package{ - &Package{Name: "lib", Version: "1.0", Architecture: "i386"}, //0 - &Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 - &Package{Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 - &Package{Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 - &Package{Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 - &Package{Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 - &Package{Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6 - &Package{Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7 + {Name: "lib", Version: "1.0", Architecture: "i386"}, //0 + {Name: "dpkg", Version: "1.7", Architecture: "i386"}, //1 + {Name: "data", Version: "1.1~bp1", Architecture: "all"}, //2 + {Name: "app", Version: "1.1~bp1", Architecture: "i386"}, //3 + {Name: "app", Version: "1.1~bp2", Architecture: "i386"}, //4 + {Name: "app", Version: "1.1~bp2", Architecture: "amd64"}, //5 + {Name: "dpkg", Version: "1.0", Architecture: "i386"}, //6 + {Name: "xyz", Version: "1.0", Architecture: "sparc"}, //7 + {Name: "dpkg", Version: "1.0", Architecture: "i386", FilesHash: 0x34445}, //8 + {Name: "app", Version: "1.1~bp2", Architecture: "i386", FilesHash: 0x44}, //9 } for _, p := range packages { + p.V06Plus = true coll.Update(p) } @@ -268,35 +272,67 @@ func (s *PackageRefListSuite) TestMerge(c *C) { listB.Add(packages[5]) listB.Add(packages[6]) + listC := NewPackageList() + listC.Add(packages[0]) + listC.Add(packages[8]) + listC.Add(packages[9]) + reflistA := NewPackageRefListFromPackageList(listA) reflistB := NewPackageRefListFromPackageList(listB) + reflistC := NewPackageRefListFromPackageList(listC) - mergeAB := reflistA.Merge(reflistB, true) - mergeBA := reflistB.Merge(reflistA, true) + mergeAB := reflistA.Merge(reflistB, true, false) + mergeBA := reflistB.Merge(reflistA, true, false) + mergeAC := reflistA.Merge(reflistC, true, false) + mergeBC := reflistB.Merge(reflistC, true, false) + mergeCB := reflistC.Merge(reflistB, true, false) c.Check(toStrSlice(mergeAB), DeepEquals, - []string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 lib 1.0", "Psparc xyz 1.0"}) + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 dpkg 1.0 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"}) c.Check(toStrSlice(mergeBA), DeepEquals, - []string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"}) + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"}) + c.Check(toStrSlice(mergeAC), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"}) + c.Check(toStrSlice(mergeBC), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000"}) + c.Check(toStrSlice(mergeCB), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 dpkg 1.0 00000000", "Pi386 lib 1.0 00000000"}) - mergeABall := reflistA.Merge(reflistB, false) - mergeBAall := reflistB.Merge(reflistA, false) + mergeABall := reflistA.Merge(reflistB, false, false) + mergeBAall := reflistB.Merge(reflistA, false, false) + mergeACall := reflistA.Merge(reflistC, false, false) + mergeBCall := reflistB.Merge(reflistC, false, false) + mergeCBall := reflistC.Merge(reflistB, false, false) c.Check(mergeABall, DeepEquals, mergeBAall) c.Check(toStrSlice(mergeBAall), DeepEquals, - []string{"Pall data 1.1~bp1", "Pamd64 app 1.1~bp2", "Pi386 app 1.1~bp1", "Pi386 app 1.1~bp2", "Pi386 dpkg 1.0", "Pi386 dpkg 1.7", "Pi386 lib 1.0", "Psparc xyz 1.0"}) + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000000", + "Pi386 dpkg 1.0 00000000", "Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"}) + + c.Check(mergeBCall, Not(DeepEquals), mergeCBall) + c.Check(toStrSlice(mergeACall), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pi386 app 1.1~bp1 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", + "Pi386 dpkg 1.7 00000000", "Pi386 lib 1.0 00000000", "Psparc xyz 1.0 00000000"}) + c.Check(toStrSlice(mergeBCall), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044", "Pi386 dpkg 1.0 00034445", + "Pi386 lib 1.0 00000000"}) + + mergeBCwithConflicts := reflistB.Merge(reflistC, false, true) + c.Check(toStrSlice(mergeBCwithConflicts), DeepEquals, + []string{"Pall data 1.1~bp1 00000000", "Pamd64 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000000", "Pi386 app 1.1~bp2 00000044", + "Pi386 dpkg 1.0 00000000", "Pi386 dpkg 1.0 00034445", "Pi386 lib 1.0 00000000"}) } func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) { packages := []*Package{ - &Package{Name: "lib", Version: "1.0", Architecture: "i386"}, - &Package{Name: "lib", Version: "1.2~bp1", Architecture: "i386"}, - &Package{Name: "lib", Version: "1.2", Architecture: "i386"}, - &Package{Name: "dpkg", Version: "1.2", Architecture: "i386"}, - &Package{Name: "dpkg", Version: "1.3", Architecture: "i386"}, - &Package{Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"}, - &Package{Name: "dpkg", Version: "1.5", Architecture: "i386"}, - &Package{Name: "dpkg", Version: "1.6", Architecture: "i386"}, + {Name: "lib", Version: "1.0", Architecture: "i386"}, + {Name: "lib", Version: "1.2~bp1", Architecture: "i386"}, + {Name: "lib", Version: "1.2", Architecture: "i386"}, + {Name: "dpkg", Version: "1.2", Architecture: "i386"}, + {Name: "dpkg", Version: "1.3", Architecture: "i386"}, + {Name: "dpkg", Version: "1.3~bp2", Architecture: "i386"}, + {Name: "dpkg", Version: "1.5", Architecture: "i386"}, + {Name: "dpkg", Version: "1.6", Architecture: "i386"}, } rl := NewPackageList() @@ -310,7 +346,7 @@ func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) { rl.Add(packages[7]) result := NewPackageRefListFromPackageList(rl) - FilterLatestRefs(result) + result.FilterLatestRefs() c.Check(toStrSlice(result), DeepEquals, []string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"}) diff --git a/src/github.com/smira/aptly/deb/remote.go b/src/github.com/smira/aptly/deb/remote.go index c109a535..881fe292 100644 --- a/src/github.com/smira/aptly/deb/remote.go +++ b/src/github.com/smira/aptly/deb/remote.go @@ -14,8 +14,10 @@ import ( "os" "path" "path/filepath" + "sort" "strconv" "strings" + "sync" "syscall" "time" ) @@ -56,6 +58,8 @@ type RemoteRepo struct { Filter string // FilterWithDeps to include dependencies from filter query FilterWithDeps bool + // SkipComponentCheck skips component list verification + SkipComponentCheck bool // Status marks state of repository (being updated, no action) Status int // WorkerPID is PID of the process modifying the mirror (if any) @@ -307,6 +311,9 @@ ok: if !repo.IsFlat() { architectures := strings.Split(stanza["Architectures"], " ") + sort.Strings(architectures) + // "source" architecture is never present, despite Release file claims + architectures = utils.StrSlicesSubstract(architectures, []string{"source"}) if len(repo.Architectures) == 0 { repo.Architectures = architectures } else { @@ -318,14 +325,19 @@ ok: } components := strings.Split(stanza["Components"], " ") - for i := range components { - components[i] = path.Base(components[i]) + if strings.Contains(repo.Distribution, "/") { + distributionLast := path.Base(repo.Distribution) + "/" + for i := range components { + if strings.HasPrefix(components[i], distributionLast) { + components[i] = components[i][len(distributionLast):] + } + } } if len(repo.Components) == 0 { repo.Components = components - } else { + } else if !repo.SkipComponentCheck { err = utils.StringsIsSubset(repo.Components, components, - fmt.Sprintf("component %%s not available in repo %s", repo)) + fmt.Sprintf("component %%s not available in repo %s, use -force-components to override", repo)) if err != nil { return err } @@ -380,6 +392,8 @@ ok: return err } + delete(stanza, "SHA512") + repo.Meta = stanza return nil @@ -454,7 +468,11 @@ func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly. } err = repo.packageList.Add(p) if err != nil { - return err + if _, ok := err.(*PackageConflictError); ok { + progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p) + } else { + return err + } } err = collectionFactory.PackageCollection().Update(p) @@ -593,6 +611,7 @@ func (repo *RemoteRepo) RefKey() []byte { // RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos type RemoteRepoCollection struct { + *sync.RWMutex db database.Storage list []*RemoteRepo } @@ -600,7 +619,8 @@ type RemoteRepoCollection struct { // NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection { result := &RemoteRepoCollection{ - db: db, + RWMutex: &sync.RWMutex{}, + db: db, } blobs := db.FetchByPrefix([]byte("R")) diff --git a/src/github.com/smira/aptly/deb/remote_test.go b/src/github.com/smira/aptly/deb/remote_test.go index 01ab3d42..4bdfbbaf 100644 --- a/src/github.com/smira/aptly/deb/remote_test.go +++ b/src/github.com/smira/aptly/deb/remote_test.go @@ -10,9 +10,10 @@ import ( "github.com/smira/aptly/utils" "io" "io/ioutil" - . "launchpad.net/gocheck" "os" "sort" + + . "gopkg.in/check.v1" ) type NullVerifier struct { @@ -192,7 +193,7 @@ func (s *RemoteRepoSuite) TestFetch(c *C) { func (s *RemoteRepoSuite) TestFetchNullVerifier1(c *C) { downloader := http.NewFakeDownloader() - downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", errors.New("404")) + downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/InRelease", &http.HTTPError{Code: 404}) downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile) downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release.gpg", "GPG") @@ -252,8 +253,8 @@ func (s *RemoteRepoSuite) TestDownload(c *C) { err := s.repo.Fetch(s.downloader, nil) c.Assert(err, IsNil) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404")) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404")) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.HTTPError{Code: 404}) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.HTTPError{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) @@ -281,11 +282,11 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { err := s.repo.Fetch(s.downloader, nil) c.Assert(err, IsNil) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", errors.New("HTTP 404")) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", errors.New("HTTP 404")) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.HTTPError{Code: 404}) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.HTTPError{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", errors.New("HTTP 404")) - s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", errors.New("HTTP 404")) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.bz2", &http.HTTPError{Code: 404}) + s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources.gz", &http.HTTPError{Code: 404}) s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/source/Sources", exampleSourcesFile) err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, s.collectionFactory, false) @@ -322,8 +323,8 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) { func (s *RemoteRepoSuite) TestDownloadFlat(c *C) { downloader := http.NewFakeDownloader() downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile) - downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404")) - downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404")) + downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", &http.HTTPError{Code: 404}) + downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", &http.HTTPError{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) err := s.flat.Fetch(downloader, nil) @@ -352,11 +353,11 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) { downloader := http.NewFakeDownloader() downloader.ExpectResponse("http://repos.express42.com/virool/precise/Release", exampleReleaseFile) - downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", errors.New("HTTP 404")) - downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", errors.New("HTTP 404")) + downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.bz2", &http.HTTPError{Code: 404}) + downloader.ExpectError("http://repos.express42.com/virool/precise/Packages.gz", &http.HTTPError{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Packages", examplePackagesFile) - downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", errors.New("HTTP 404")) - downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", errors.New("HTTP 404")) + downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.bz2", &http.HTTPError{Code: 404}) + downloader.ExpectError("http://repos.express42.com/virool/precise/Sources.gz", &http.HTTPError{Code: 404}) downloader.ExpectResponse("http://repos.express42.com/virool/precise/Sources", exampleSourcesFile) err := s.flat.Fetch(downloader, nil) diff --git a/src/github.com/smira/aptly/deb/snapshot.go b/src/github.com/smira/aptly/deb/snapshot.go index 0c273480..3a7ff829 100644 --- a/src/github.com/smira/aptly/deb/snapshot.go +++ b/src/github.com/smira/aptly/deb/snapshot.go @@ -9,22 +9,24 @@ import ( "github.com/smira/aptly/utils" "github.com/ugorji/go/codec" "log" + "sort" "strings" + "sync" "time" ) // Snapshot is immutable state of repository: list of packages type Snapshot struct { // Persisten internal ID - UUID string + UUID string `json:"-"` // Human-readable name Name string // Date of creation CreatedAt time.Time // Source: kind + ID - SourceKind string - SourceIDs []string + SourceKind string `json:"-"` + SourceIDs []string `json:"-"` // Description of how snapshot was created Description string @@ -131,12 +133,12 @@ func (s *Snapshot) Decode(input []byte) error { if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") { // probably it is broken DB from go < 1.2, try decoding w/o time.Time var snapshot11 struct { - UUID string - Name string + UUID string + Name string CreatedAt []byte - SourceKind string - SourceIDs []string + SourceKind string + SourceIDs []string Description string } @@ -160,6 +162,7 @@ func (s *Snapshot) Decode(input []byte) error { // SnapshotCollection does listing, updating/adding/deleting of Snapshots type SnapshotCollection struct { + *sync.RWMutex db database.Storage list []*Snapshot } @@ -167,7 +170,8 @@ type SnapshotCollection struct { // NewSnapshotCollection loads Snapshots from DB and makes up collection func NewSnapshotCollection(db database.Storage) *SnapshotCollection { result := &SnapshotCollection{ - db: db, + RWMutex: &sync.RWMutex{}, + db: db, } blobs := db.FetchByPrefix([]byte("S")) @@ -293,6 +297,23 @@ func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) err return err } +// ForEachSorted runs method for each snapshot following some sort order +func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error { + sorter, err := newSnapshotSorter(sortMethod, collection) + if err != nil { + return err + } + + for _, i := range sorter.list { + err = handler(collection.list[i]) + if err != nil { + return err + } + } + + return nil +} + // Len returns number of snapshots in collection // ForEach runs method for each snapshot func (collection *SnapshotCollection) Len() int { @@ -324,3 +345,55 @@ func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error { return collection.db.Delete(snapshot.RefKey()) } + +// Snapshot sorting methods +const ( + SortName = iota + SortTime +) + +type snapshotSorter struct { + list []int + collection *SnapshotCollection + sortMethod int +} + +func newSnapshotSorter(sortMethod string, collection *SnapshotCollection) (*snapshotSorter, error) { + s := &snapshotSorter{collection: collection} + + switch sortMethod { + case "time", "Time": + s.sortMethod = SortTime + case "name", "Name": + s.sortMethod = SortName + default: + return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod) + } + + s.list = make([]int, len(collection.list)) + for i := range s.list { + s.list[i] = i + } + + sort.Sort(s) + + return s, nil +} + +func (s *snapshotSorter) Swap(i, j int) { + s.list[i], s.list[j] = s.list[j], s.list[i] +} + +func (s *snapshotSorter) Less(i, j int) bool { + switch s.sortMethod { + case SortName: + return s.collection.list[s.list[i]].Name < s.collection.list[s.list[j]].Name + case SortTime: + return s.collection.list[s.list[i]].CreatedAt.Before(s.collection.list[s.list[j]].CreatedAt) + } + panic("unknown sort method") +} + +func (s *snapshotSorter) Len() int { + return len(s.list) +} diff --git a/src/github.com/smira/aptly/deb/snapshot_test.go b/src/github.com/smira/aptly/deb/snapshot_test.go index 83db0853..360f9ecb 100644 --- a/src/github.com/smira/aptly/deb/snapshot_test.go +++ b/src/github.com/smira/aptly/deb/snapshot_test.go @@ -3,7 +3,8 @@ package deb import ( "errors" "github.com/smira/aptly/database" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type SnapshotSuite struct { diff --git a/src/github.com/smira/aptly/deb/version_test.go b/src/github.com/smira/aptly/deb/version_test.go index 53a47b95..03c40c29 100644 --- a/src/github.com/smira/aptly/deb/version_test.go +++ b/src/github.com/smira/aptly/deb/version_test.go @@ -1,7 +1,7 @@ package deb import ( - . "launchpad.net/gocheck" + . "gopkg.in/check.v1" ) type VersionSuite struct { diff --git a/src/github.com/smira/aptly/files/files_test.go b/src/github.com/smira/aptly/files/files_test.go index 523bf714..da7dc358 100644 --- a/src/github.com/smira/aptly/files/files_test.go +++ b/src/github.com/smira/aptly/files/files_test.go @@ -1,8 +1,9 @@ package files import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/files/package_pool.go b/src/github.com/smira/aptly/files/package_pool.go index 385ed050..650d8b5f 100644 --- a/src/github.com/smira/aptly/files/package_pool.go +++ b/src/github.com/smira/aptly/files/package_pool.go @@ -7,10 +7,12 @@ import ( "io/ioutil" "os" "path/filepath" + "sync" ) // PackagePool is deduplicated storage of package files on filesystem type PackagePool struct { + sync.Mutex rootPath string } @@ -50,6 +52,9 @@ func (pool *PackagePool) Path(filename string, hashMD5 string) (string, error) { // FilepathList returns file paths of all the files in the pool func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) { + pool.Lock() + defer pool.Unlock() + dirs, err := ioutil.ReadDir(pool.rootPath) if err != nil { if os.IsNotExist(err) { @@ -93,6 +98,9 @@ func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) // Remove deletes file in package pool returns its size func (pool *PackagePool) Remove(path string) (size int64, err error) { + pool.Lock() + defer pool.Unlock() + path = filepath.Join(pool.rootPath, path) info, err := os.Stat(path) @@ -106,6 +114,9 @@ func (pool *PackagePool) Remove(path string) (size int64, err error) { // Import copies file into package pool func (pool *PackagePool) Import(path string, hashMD5 string) error { + pool.Lock() + defer pool.Unlock() + source, err := os.Open(path) if err != nil { return err @@ -128,12 +139,15 @@ func (pool *PackagePool) Import(path string, hashMD5 string) error { // unable to stat target location? return err } - // file doesn't exist, that's ok } else { + // target already exists if targetInfo.Size() != sourceInfo.Size() { // trying to overwrite file? return fmt.Errorf("unable to import into pool: file %s already exists", poolPath) } + + // assume that target is already there + return nil } // create subdirs as necessary diff --git a/src/github.com/smira/aptly/files/package_pool_test.go b/src/github.com/smira/aptly/files/package_pool_test.go index 9042fee3..0f636d90 100644 --- a/src/github.com/smira/aptly/files/package_pool_test.go +++ b/src/github.com/smira/aptly/files/package_pool_test.go @@ -2,10 +2,11 @@ package files import ( "io/ioutil" - . "launchpad.net/gocheck" "os" "path/filepath" "runtime" + + . "gopkg.in/check.v1" ) type PackagePoolSuite struct { diff --git a/src/github.com/smira/aptly/files/public_test.go b/src/github.com/smira/aptly/files/public_test.go index b619465f..f613b37d 100644 --- a/src/github.com/smira/aptly/files/public_test.go +++ b/src/github.com/smira/aptly/files/public_test.go @@ -2,10 +2,11 @@ package files import ( "io/ioutil" - . "launchpad.net/gocheck" "os" "path/filepath" "syscall" + + . "gopkg.in/check.v1" ) type PublishedStorageSuite struct { diff --git a/src/github.com/smira/aptly/http/download.go b/src/github.com/smira/aptly/http/download.go index adf3c914..ee117f31 100644 --- a/src/github.com/smira/aptly/http/download.go +++ b/src/github.com/smira/aptly/http/download.go @@ -16,6 +16,17 @@ import ( "strings" ) +// HTTPError is download error connected to HTTP code +type HTTPError struct { + Code int + URL string +} + +// Error +func (e *HTTPError) Error() string { + return fmt.Sprintf("HTTP code %d while fetching %s", e.Code, e.URL) +} + // Check interface var ( _ aptly.Downloader = (*downloaderImpl)(nil) @@ -129,7 +140,19 @@ func (downloader *downloaderImpl) DownloadWithChecksum(url string, destination s func (downloader *downloaderImpl) handleTask(task *downloadTask) { downloader.progress.Printf("Downloading %s...\n", task.url) - resp, err := downloader.client.Get(task.url) + req, err := http.NewRequest("GET", task.url, nil) + if err != nil { + task.result <- fmt.Errorf("%s: %s", task.url, err) + return + } + + proxyURL, _ := downloader.client.Transport.(*http.Transport).Proxy(req) + if proxyURL == nil && (req.URL.Scheme == "http" || req.URL.Scheme == "https") { + req.URL.Opaque = strings.Replace(req.URL.RequestURI(), "+", "%2b", -1) + req.URL.RawQuery = "" + } + + resp, err := downloader.client.Do(req) if err != nil { task.result <- fmt.Errorf("%s: %s", task.url, err) return @@ -139,7 +162,7 @@ func (downloader *downloaderImpl) handleTask(task *downloadTask) { } if resp.StatusCode < 200 || resp.StatusCode > 299 { - task.result <- fmt.Errorf("HTTP code %d while fetching %s", resp.StatusCode, task.url) + task.result <- &HTTPError{Code: resp.StatusCode, URL: task.url} return } @@ -307,13 +330,16 @@ func DownloadTryCompression(downloader aptly.Downloader, url string, expectedChe } if err != nil { - continue + if err1, ok := err.(*HTTPError); ok && (err1.Code == 404 || err1.Code == 403) { + continue + } + return nil, nil, err } var uncompressed io.Reader uncompressed, err = method.transformation(file) if err != nil { - continue + return nil, nil, err } return uncompressed, file, err diff --git a/src/github.com/smira/aptly/http/download_test.go b/src/github.com/smira/aptly/http/download_test.go index cfe51c02..d5eda5b4 100644 --- a/src/github.com/smira/aptly/http/download_test.go +++ b/src/github.com/smira/aptly/http/download_test.go @@ -8,12 +8,13 @@ import ( "github.com/smira/aptly/utils" "io" "io/ioutil" - . "launchpad.net/gocheck" "net" "net/http" "os" "runtime" "time" + + . "gopkg.in/check.v1" ) type DownloaderSuite struct { @@ -210,9 +211,9 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) { var buf []byte expectedChecksums := map[string]utils.ChecksumInfo{ - "file.bz2": utils.ChecksumInfo{Size: int64(len(bzipData))}, - "file.gz": utils.ChecksumInfo{Size: int64(len(gzipData))}, - "file": utils.ChecksumInfo{Size: int64(len(rawData))}, + "file.bz2": {Size: int64(len(bzipData))}, + "file.gz": {Size: int64(len(gzipData))}, + "file": {Size: int64(len(rawData))}, } // bzip2 only available @@ -229,7 +230,7 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) { // bzip2 not available, but gz is buf = make([]byte, 4) d = NewFakeDownloader() - d.ExpectError("http://example.com/file.bz2", errors.New("404")) + d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) d.ExpectResponse("http://example.com/file.gz", gzipData) r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false) c.Assert(err, IsNil) @@ -241,8 +242,8 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) { // bzip2 & gzip not available, but raw is buf = make([]byte, 4) d = NewFakeDownloader() - d.ExpectError("http://example.com/file.bz2", errors.New("404")) - d.ExpectError("http://example.com/file.gz", errors.New("404")) + d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) + d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404}) d.ExpectResponse("http://example.com/file", rawData) r, file, err = DownloadTryCompression(d, "http://example.com/file", expectedChecksums, false) c.Assert(err, IsNil) @@ -254,14 +255,10 @@ func (s *DownloaderSuite) TestDownloadTryCompression(c *C) { // gzip available, but broken buf = make([]byte, 4) d = NewFakeDownloader() - d.ExpectError("http://example.com/file.bz2", errors.New("404")) + d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) d.ExpectResponse("http://example.com/file.gz", "x") - d.ExpectResponse("http://example.com/file", "recovered") r, file, err = DownloadTryCompression(d, "http://example.com/file", nil, false) - c.Assert(err, IsNil) - defer file.Close() - io.ReadFull(r, buf) - c.Assert(string(buf), Equals, "reco") + c.Assert(err, ErrorMatches, "unexpected EOF") c.Assert(d.Empty(), Equals, true) } @@ -271,16 +268,16 @@ func (s *DownloaderSuite) TestDownloadTryCompressionErrors(c *C) { c.Assert(err, ErrorMatches, "unexpected request.*") d = NewFakeDownloader() - d.ExpectError("http://example.com/file.bz2", errors.New("404")) - d.ExpectError("http://example.com/file.gz", errors.New("404")) + d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) + d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404}) d.ExpectError("http://example.com/file", errors.New("403")) _, _, err = DownloadTryCompression(d, "http://example.com/file", nil, false) c.Assert(err, ErrorMatches, "403") d = NewFakeDownloader() - d.ExpectError("http://example.com/file.bz2", errors.New("404")) - d.ExpectError("http://example.com/file.gz", errors.New("404")) + d.ExpectError("http://example.com/file.bz2", &HTTPError{Code: 404}) + d.ExpectError("http://example.com/file.gz", &HTTPError{Code: 404}) d.ExpectResponse("http://example.com/file", rawData) - _, _, err = DownloadTryCompression(d, "http://example.com/file", map[string]utils.ChecksumInfo{"file": utils.ChecksumInfo{Size: 7}}, false) + _, _, err = DownloadTryCompression(d, "http://example.com/file", map[string]utils.ChecksumInfo{"file": {Size: 7}}, false) c.Assert(err, ErrorMatches, "checksums don't match.*") } diff --git a/src/github.com/smira/aptly/http/http_test.go b/src/github.com/smira/aptly/http/http_test.go index c19d909a..2c2d087d 100644 --- a/src/github.com/smira/aptly/http/http_test.go +++ b/src/github.com/smira/aptly/http/http_test.go @@ -1,8 +1,9 @@ package http import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/man/aptly.1 b/src/github.com/smira/aptly/man/aptly.1 index 642ade7f..11af4701 100644 --- a/src/github.com/smira/aptly/man/aptly.1 +++ b/src/github.com/smira/aptly/man/aptly.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "APTLY" "1" "October 2014" "" "" +.TH "APTLY" "1" "February 2015" "" "" . .SH "NAME" \fBaptly\fR \- Debian repository management tool @@ -52,7 +52,7 @@ Configuration file is stored in JSON format (default values shown below): "test": { "region": "us\-east\-1", "bucket": "repo", - "awsAccessKeyID": "" + "awsAccessKeyID": "", "awsSecretAccessKey": "", "prefix": "", "acl": "public\-read", @@ -60,6 +60,18 @@ Configuration file is stored in JSON format (default values shown below): "encryptionMethod": "", "plusWorkaround": false } + }, + "SwiftPublishEndpoints": { + "test": { + "container": "repo", + "osname": "", + "password": "", + "prefix": "", + "authurl": "", + "tenant": "", + "tenantid": "" + } + } } . .fi @@ -162,6 +174,35 @@ In order to publish to S3, specify endpoint as \fBs3:endpoint\-name:\fR before p .P \fBaptly publish snapshot wheezy\-main s3:test:\fR . +.SH "OPENSTACK SWIFT PUBLISHING ENDPOINTS" +aptly could be configured to publish repository directly to OpenStack Swift\. First, publishing endpoints should be described in aptly configuration file\. Each endpoint has name and associated settings: +. +.TP +\fBcontainer\fR +container name +. +.TP +\fBprefix\fR +(optional) do publishing under specified prefix in the container, defaults to no prefix (container root) +. +.TP +\fBosname\fR, \fBpassword\fR +(optional) OpenStack credentials to access Keystone\. If not supplied, environment variables \fBOS_USERNAME\fR and \fBOS_PASSWORD\fR are used\. +. +.TP +\fBtenant\fR, \fBtenantid\fR +(optional) OpenStack tenant name and id (in order to use v2 authentication)\. +. +.TP +\fBauthurl\fR +(optional) the full url of Keystone server (including port, and version)\. example \fBhttp://identity\.example\.com:5000/v2\.0\fR +. +.P +In order to publish to Swift, specify endpoint as \fBswift:endpoint\-name:\fR before publishing prefix on the command line, e\.g\.: +. +.P +\fBaptly publish snapshot jessie\-main swift:test:\fR +. .SH "PACKAGE QUERY" Some commands accept package queries to identify list of packages to process\. Package query syntax almost matches \fBreprepro\fR query language\. Query consists of the following simple terms: . @@ -315,6 +356,10 @@ filter packages in mirror when filtering, include dependencies of matching packages as well . .TP +\-\fBforce\-components\fR=false +(only with component list) skip check that requested components are listed in Release file +. +.TP \-\fBignore\-signatures\fR=false disable verification of Release file signatures . @@ -1102,6 +1147,10 @@ $ aptly publish repo testing Options: . .TP +\-\fBbatch\fR=false +run GPG with detached tty +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1181,6 +1230,10 @@ $ aptly publish snapshot wheezy\-main Options: . .TP +\-\fBbatch\fR=false +run GPG with detached tty +. +.TP \-\fBcomponent\fR= component name to publish (for multi\-component publishing, separate components with commas) . @@ -1228,7 +1281,7 @@ don\(cqt sign Release files with GPG \fBaptly\fR \fBpublish\fR \fBswitch\fR \fIdistribution\fR [[\fIendpoint\fR:]\fIprefix\fR] \fInew\-snapshot\fR . .P -Command switches in\-place published repository with new snapshot contents\. All publishing parameters are preserved (architecture list, distribution, component)\. +Command switches in\-place published snapshots with new snapshot contents\. All publishing parameters are preserved (architecture list, distribution, component)\. . .P For multiple component repositories, flag \-component should be given with list of components to update\. Corresponding snapshots should be given in the same order, e\.g\.: @@ -1237,7 +1290,7 @@ For multiple component repositories, flag \-component should be given with list . .nf -aptly publish update \-component=main,contrib wheezy wh\-main wh\-contrib +aptly publish switch \-component=main,contrib wheezy wh\-main wh\-contrib . .fi . @@ -1250,16 +1303,23 @@ Example: . .nf -$ aptly publish update wheezy ppa wheezy\-7\.5 +$ aptly publish switch wheezy ppa wheezy\-7\.5 . .fi . .IP "" 0 . .P +This command would switch published repository (with one component) named ppa/wheezy (prefix ppa, dsitribution wheezy to new snapshot wheezy\-7\.5)\. +. +.P Options: . .TP +\-\fBbatch\fR=false +run GPG with detached tty +. +.TP \-\fBcomponent\fR= component names to update (for multi\-component publishing, separate components with commas) . @@ -1317,6 +1377,10 @@ $ aptly publish update wheezy ppa Options: . .TP +\-\fBbatch\fR=false +run GPG with detached tty +. +.TP \-\fBforce\-overwrite\fR=false overwrite files in package pool in case of mismatch . @@ -1363,7 +1427,7 @@ $ aptly package search \(cq$Architecture (i386), Name (% *\-dev)\(cq . .IP "" 0 . -.SH "SHOW DETAILS ABOUT PACKAGES MATCING QUERY" +.SH "SHOW DETAILS ABOUT PACKAGES MATCHING QUERY" \fBaptly\fR \fBpackage\fR \fBshow\fR \fIpackage\-query\fR . .P @@ -1448,6 +1512,49 @@ Example: .P $ aptly graph . +.SH "SHOW CURRENT APTLY\(cqS CONFIG" +\fBaptly\fR \fBconfig\fR \fBshow\fR +. +.P +Command show displays the current aptly configuration\. +. +.P +Example: +. +.P +$ aptly config show +. +.SH "RUN APTLY TASKS" +\fBaptly\fR \fBtask\fR \fBrun\fR \-filename=\fIfilename\fR \fB|\fR \fIcommand1\fR, \fIcommand2\fR, \fB\|\.\|\.\|\.\fR +. +.P +Command helps organise multiple aptly commands in one single aptly task, running as single thread\. +. +.P +Example: +. +.IP "" 4 +. +.nf + + $ aptly task run + > repo create local + > repo add local pkg1 + > publish repo local + > serve + > +. +.fi +. +.IP "" 0 +. +.P +Options: +. +.TP +\-\fBfilename\fR= +specifies the filename that contains the commands to run +. .SH "ENVIRONMENT" If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\. . diff --git a/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl b/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl index b2674547..3d6528b2 100644 --- a/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl +++ b/src/github.com/smira/aptly/man/aptly.1.ronn.tmpl @@ -44,7 +44,7 @@ Configuration file is stored in JSON format (default values shown below): "test": { "region": "us-east-1", "bucket": "repo", - "awsAccessKeyID": "" + "awsAccessKeyID": "", "awsSecretAccessKey": "", "prefix": "", "acl": "public-read", @@ -52,6 +52,18 @@ Configuration file is stored in JSON format (default values shown below): "encryptionMethod": "", "plusWorkaround": false } + }, + "SwiftPublishEndpoints": { + "test": { + "container": "repo", + "osname": "", + "password": "", + "prefix": "", + "authurl": "", + "tenant": "", + "tenantid": "" + } + } } Options: @@ -101,6 +113,9 @@ Options: * `S3PublishEndpoints`: configuration of Amazon S3 publishing endpoints (see below) + * `SwiftPublishEndpoints`: + configuration of OpenStack Swift publishing endpoints (see below) + ## S3 PUBLISHING ENDPOINTS aptly could be configured to publish repository directly to Amazon S3. First, publishing @@ -144,6 +159,31 @@ publishing prefix on the command line, e.g.: `aptly publish snapshot wheezy-main s3:test:` +## OPENSTACK SWIFT PUBLISHING ENDPOINTS + +aptly could be configured to publish repository directly to OpenStack Swift. First, +publishing endpoints should be described in aptly configuration file. Each endpoint +has name and associated settings: + + * `container`: + container name + * `prefix`: + (optional) do publishing under specified prefix in the container, defaults to + no prefix (container root) + * `osname`, `password`: + (optional) OpenStack credentials to access Keystone. If not supplied, + environment variables `OS_USERNAME` and `OS_PASSWORD` are used. + * `tenant`, `tenantid`: + (optional) OpenStack tenant name and id (in order to use v2 authentication). + * `authurl`: + (optional) the full url of Keystone server (including port, and version). + example `http://identity.example.com:5000/v2.0` + +In order to publish to Swift, specify endpoint as `swift:endpoint-name:` before +publishing prefix on the command line, e.g.: + + `aptly publish snapshot jessie-main swift:test:` + ## PACKAGE QUERY Some commands accept package queries to identify list of packages to process. @@ -245,6 +285,10 @@ When specified on command line, query may have to be quoted according to shell r {{template "command" findCommand . "graph"}} +{{template "command" findCommand . "config"}} + +{{template "command" findCommand . "task"}} + ## ENVIRONMENT If environment variable `HTTP_PROXY` is set `aptly` would use its value @@ -295,4 +339,4 @@ Options: * -`{{.Name}}`={{.DefValue}}: {{.Usage}} {{end}} -{{end}} \ No newline at end of file +{{end}} diff --git a/src/github.com/smira/aptly/query/lex_test.go b/src/github.com/smira/aptly/query/lex_test.go index 8eef6513..27d8b04b 100644 --- a/src/github.com/smira/aptly/query/lex_test.go +++ b/src/github.com/smira/aptly/query/lex_test.go @@ -2,7 +2,8 @@ package query import ( "fmt" - . "launchpad.net/gocheck" + + . "gopkg.in/check.v1" ) type LexerSuite struct { diff --git a/src/github.com/smira/aptly/query/query_test.go b/src/github.com/smira/aptly/query/query_test.go index 84989efd..2097c7ab 100644 --- a/src/github.com/smira/aptly/query/query_test.go +++ b/src/github.com/smira/aptly/query/query_test.go @@ -1,8 +1,9 @@ package query import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/query/syntax_test.go b/src/github.com/smira/aptly/query/syntax_test.go index 3e720f93..b3d5ce41 100644 --- a/src/github.com/smira/aptly/query/syntax_test.go +++ b/src/github.com/smira/aptly/query/syntax_test.go @@ -2,8 +2,9 @@ package query import ( "github.com/smira/aptly/deb" - . "launchpad.net/gocheck" "regexp" + + . "gopkg.in/check.v1" ) type SyntaxSuite struct { diff --git a/src/github.com/smira/aptly/s3/public_test.go b/src/github.com/smira/aptly/s3/public_test.go index d2ab8ee0..0f6d2221 100644 --- a/src/github.com/smira/aptly/s3/public_test.go +++ b/src/github.com/smira/aptly/s3/public_test.go @@ -5,9 +5,10 @@ import ( "github.com/mitchellh/goamz/s3/s3test" "github.com/smira/aptly/files" "io/ioutil" - . "launchpad.net/gocheck" "os" "path/filepath" + + . "gopkg.in/check.v1" ) type PublishedStorageSuite struct { diff --git a/src/github.com/smira/aptly/s3/s3_test.go b/src/github.com/smira/aptly/s3/s3_test.go index 353b6b52..9c45fab0 100644 --- a/src/github.com/smira/aptly/s3/s3_test.go +++ b/src/github.com/smira/aptly/s3/s3_test.go @@ -1,8 +1,9 @@ package s3 import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests diff --git a/src/github.com/smira/aptly/swift/public.go b/src/github.com/smira/aptly/swift/public.go new file mode 100644 index 00000000..76d1fd29 --- /dev/null +++ b/src/github.com/smira/aptly/swift/public.go @@ -0,0 +1,229 @@ +package swift + +import ( + "encoding/json" + "fmt" + "github.com/ncw/swift" + "github.com/smira/aptly/aptly" + "github.com/smira/aptly/files" + "net/http" + "os" + "path/filepath" + "time" +) + +// PublishedStorage abstract file system with published files (actually hosted on Swift) +type PublishedStorage struct { + conn swift.Connection + container string + prefix string + supportBulkDelete bool +} + +type swiftInfo map[string]interface{} + +// Check interface +var ( + _ aptly.PublishedStorage = (*PublishedStorage)(nil) +) + +// NewPublishedStorage creates new instance of PublishedStorage with specified Swift access +// keys, tenant and tenantId +func NewPublishedStorage(username string, password string, authURL string, tenant string, tenantID string, container string, prefix string) (*PublishedStorage, error) { + if username == "" { + if username = os.Getenv("OS_USERNAME"); username == "" { + username = os.Getenv("ST_USER") + } + } + if password == "" { + if password = os.Getenv("OS_PASSWORD"); password == "" { + password = os.Getenv("ST_KEY") + } + } + if authURL == "" { + if authURL = os.Getenv("OS_AUTH_URL"); authURL == "" { + authURL = os.Getenv("ST_AUTH") + } + } + if tenant == "" { + tenant = os.Getenv("OS_TENANT_NAME") + } + if tenantID == "" { + tenantID = os.Getenv("OS_TENANT_ID") + } + + ct := swift.Connection{ + UserName: username, + ApiKey: password, + AuthUrl: authURL, + UserAgent: "aptly/" + aptly.Version, + Tenant: tenant, + TenantId: tenantID, + ConnectTimeout: 60 * time.Second, + Timeout: 60 * time.Second, + } + err := ct.Authenticate() + if err != nil { + return nil, fmt.Errorf("swift authentication failed: %s", err) + } + + var bulkDelete bool + resp, err := http.Get(filepath.Join(ct.StorageUrl, "..", "..") + "/info") + if err == nil { + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + var infos swiftInfo + if decoder.Decode(&infos) == nil { + _, bulkDelete = infos["bulk_delete"] + } + } + + result := &PublishedStorage{ + conn: ct, + container: container, + prefix: prefix, + supportBulkDelete: bulkDelete, + } + + return result, nil +} + +// String +func (storage *PublishedStorage) String() string { + return fmt.Sprintf("Swift: %s:%s/%s", storage.conn.StorageUrl, storage.container, storage.prefix) +} + +// MkDir creates directory recursively under public path +func (storage *PublishedStorage) MkDir(path string) error { + // no op for Swift + return nil +} + +// PutFile puts file into published storage at specified path +func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error { + var ( + source *os.File + err error + ) + source, err = os.Open(sourceFilename) + if err != nil { + return err + } + defer source.Close() + + _, err = storage.conn.ObjectPut(storage.container, filepath.Join(storage.prefix, path), source, false, "", "", nil) + + if err != nil { + return fmt.Errorf("error uploading %s to %s: %s", sourceFilename, storage, err) + } + return nil +} + +// Remove removes single file under public path +func (storage *PublishedStorage) Remove(path string) error { + err := storage.conn.ObjectDelete(storage.container, filepath.Join(storage.prefix, path)) + + if err != nil { + return fmt.Errorf("error deleting %s from %s: %s", path, storage, err) + } + return nil +} + +// RemoveDirs removes directory structure under public path +func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { + path = filepath.Join(storage.prefix, path) + opts := swift.ObjectsOpts{ + Prefix: path, + } + + objects, err := storage.conn.ObjectNamesAll(storage.container, &opts) + if err != nil { + return fmt.Errorf("error removing dir %s from %s: %s", path, storage, err) + } + + for index, name := range objects { + objects[index] = name[len(storage.prefix):] + } + + multiDelete := true + if storage.supportBulkDelete { + _, err := storage.conn.BulkDelete(storage.container, objects) + multiDelete = err != nil + } + if multiDelete { + for _, name := range objects { + if err := storage.conn.ObjectDelete(storage.container, name); err != nil { + return err + } + } + } + + return nil +} + +// LinkFromPool links package file from pool to dist's pool location +// +// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) +// sourcePool is instance of aptly.PackagePool +// sourcePath is filepath to package file in package pool +// +// LinkFromPool returns relative path for the published file to be included in package index +func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, + sourcePath, sourceMD5 string, force bool) error { + // verify that package pool is local pool in filesystem + _ = sourcePool.(*files.PackagePool) + + baseName := filepath.Base(sourcePath) + relPath := filepath.Join(publishedDirectory, baseName) + poolPath := filepath.Join(storage.prefix, relPath) + + var ( + info swift.Object + err error + ) + + info, _, err = storage.conn.Object(storage.container, poolPath) + if err != nil { + if err != swift.ObjectNotFound { + return fmt.Errorf("error getting information about %s from %s: %s", poolPath, storage, err) + } + } else { + if !force && info.Hash != sourceMD5 { + return fmt.Errorf("error putting file to %s: file already exists and is different: %s", poolPath, storage) + + } + } + + return storage.PutFile(relPath, sourcePath) +} + +// Filelist returns list of files under prefix +func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { + prefix = filepath.Join(storage.prefix, prefix) + if prefix != "" { + prefix += "/" + } + opts := swift.ObjectsOpts{ + Prefix: prefix, + } + contents, err := storage.conn.ObjectNamesAll(storage.container, &opts) + if err != nil { + return nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, storage, err) + } + + for index, name := range contents { + contents[index] = name[len(prefix):] + } + + return contents, nil +} + +// RenameFile renames (moves) file +func (storage *PublishedStorage) RenameFile(oldName, newName string) error { + err := storage.conn.ObjectMove(storage.container, filepath.Join(storage.prefix, oldName), storage.container, filepath.Join(storage.prefix, newName)) + if err != nil { + return fmt.Errorf("error copying %s -> %s in %s: %s", oldName, newName, storage, err) + } + + return nil +} diff --git a/src/github.com/smira/aptly/swift/public_test.go b/src/github.com/smira/aptly/swift/public_test.go new file mode 100644 index 00000000..c32175ec --- /dev/null +++ b/src/github.com/smira/aptly/swift/public_test.go @@ -0,0 +1,187 @@ +package swift + +import ( + "github.com/ncw/swift/swifttest" + "github.com/smira/aptly/files" + + . "gopkg.in/check.v1" + "io/ioutil" + "os" + "path/filepath" +) + +const ( + TestAddress = "localhost:5324" + AuthURL = "http://" + TestAddress + "/v1.0" +) + +type PublishedStorageSuite struct { + srv *swifttest.SwiftServer + storage, prefixedStorage *PublishedStorage +} + +var _ = Suite(&PublishedStorageSuite{}) + +func (s *PublishedStorageSuite) SetUpTest(c *C) { + var err error + s.srv, err = swifttest.NewSwiftServer(TestAddress) + c.Assert(err, IsNil) + c.Assert(s.srv, NotNil) + + s.storage, err = NewPublishedStorage("swifttest", "swifttest", AuthURL, "", "", "test", "") + c.Assert(err, IsNil) + + s.prefixedStorage, err = NewPublishedStorage("swifttest", "swifttest", AuthURL, "", "", "test", "lala") + c.Assert(err, IsNil) + + s.storage.conn.ContainerCreate("test", nil) +} + +func (s *PublishedStorageSuite) TearDownTest(c *C) { + s.srv.Close() +} + +func (s *PublishedStorageSuite) TestNewPublishedStorage(c *C) { + stor, err := NewPublishedStorage("swifttest", "swifttest", AuthURL, "", "", "", "") + c.Check(stor, NotNil) + c.Check(err, IsNil) +} + +func (s *PublishedStorageSuite) TestPutFile(c *C) { + dir := c.MkDir() + err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("welcome to swift!"), 0644) + c.Assert(err, IsNil) + + err = s.storage.PutFile("a/b.txt", filepath.Join(dir, "a")) + c.Check(err, IsNil) + + data, err := s.storage.conn.ObjectGetBytes("test", "a/b.txt") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("welcome to swift!")) + + err = s.prefixedStorage.PutFile("a/b.txt", filepath.Join(dir, "a")) + c.Check(err, IsNil) + + data, err = s.storage.conn.ObjectGetBytes("test", "lala/a/b.txt") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("welcome to swift!")) +} + +func (s *PublishedStorageSuite) TestFilelist(c *C) { + dir := c.MkDir() + err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("welcome to swift!"), 0644) + c.Assert(err, IsNil) + + paths := []string{"a", "b", "c", "testa", "test/a", "test/b", "lala/a", "lala/b", "lala/c"} + for _, path := range paths { + err = s.storage.PutFile(path, filepath.Join(dir, "a")) + c.Check(err, IsNil) + } + + list, err := s.storage.Filelist("") + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a", "lala/b", "lala/c", "test/a", "test/b", "testa"}) + + list, err = s.storage.Filelist("test") + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{"a", "b"}) + + list, err = s.storage.Filelist("test2") + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{}) + + list, err = s.prefixedStorage.Filelist("") + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{"a", "b", "c"}) +} + +func (s *PublishedStorageSuite) TestRemove(c *C) { + dir := c.MkDir() + err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("welcome to swift!"), 0644) + c.Assert(err, IsNil) + + err = s.storage.PutFile("a/b.txt", filepath.Join(dir, "a")) + c.Check(err, IsNil) + + err = s.storage.Remove("a/b.txt") + c.Check(err, IsNil) + + _, err = s.storage.conn.ObjectGetBytes("test", "a/b.txt") + c.Check(err, ErrorMatches, "Object Not Found") +} + +func (s *PublishedStorageSuite) TestRemoveDirs(c *C) { + c.Skip("bulk-delete not available in s3test") + + dir := c.MkDir() + err := ioutil.WriteFile(filepath.Join(dir, "a"), []byte("welcome to swift!"), 0644) + c.Assert(err, IsNil) + + paths := []string{"a", "b", "c", "testa", "test/a", "test/b", "lala/a", "lala/b", "lala/c"} + for _, path := range paths { + err = s.storage.PutFile(path, filepath.Join(dir, "a")) + c.Check(err, IsNil) + } + + err = s.storage.RemoveDirs("test", nil) + c.Check(err, IsNil) + + list, err := s.storage.Filelist("") + c.Check(err, IsNil) + c.Check(list, DeepEquals, []string{"a", "b", "c", "lala/a", "lala/b", "lala/c", "test/a", "test/b", "testa"}) +} + +func (s *PublishedStorageSuite) TestRenameFile(c *C) { + c.Skip("copy not available in s3test") +} + +func (s *PublishedStorageSuite) TestLinkFromPool(c *C) { + root := c.MkDir() + pool := files.NewPackagePool(root) + + sourcePath := filepath.Join(root, "pool/c1/df/mars-invaders_1.03.deb") + err := os.MkdirAll(filepath.Dir(sourcePath), 0755) + c.Assert(err, IsNil) + + err = ioutil.WriteFile(sourcePath, []byte("Contents"), 0644) + c.Assert(err, IsNil) + + sourcePath2 := filepath.Join(root, "pool/e9/df/mars-invaders_1.03.deb") + err = os.MkdirAll(filepath.Dir(sourcePath2), 0755) + c.Assert(err, IsNil) + + err = ioutil.WriteFile(sourcePath2, []byte("Spam"), 0644) + c.Assert(err, IsNil) + + // first link from pool + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "c1df1da7a1ce305a3b60af9d5733ac1d", false) + c.Check(err, IsNil) + + data, err := s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // duplicate link from pool + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, "c1df1da7a1ce305a3b60af9d5733ac1d", false) + c.Check(err, IsNil) + + data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // link from pool with conflict + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath2, "e9dfd31cc505d51fc26975250750deab", false) + c.Check(err, ErrorMatches, ".*file already exists and is different.*") + + data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Contents")) + + // link from pool with conflict and force + err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath2, "e9dfd31cc505d51fc26975250750deab", true) + c.Check(err, IsNil) + + data, err = s.storage.conn.ObjectGetBytes("test", "pool/main/m/mars-invaders/mars-invaders_1.03.deb") + c.Check(err, IsNil) + c.Check(data, DeepEquals, []byte("Spam")) +} diff --git a/src/github.com/smira/aptly/swift/swift.go b/src/github.com/smira/aptly/swift/swift.go new file mode 100644 index 00000000..dc4be30a --- /dev/null +++ b/src/github.com/smira/aptly/swift/swift.go @@ -0,0 +1,2 @@ +// Package swift handles publishing to OpenStack Swift +package swift diff --git a/src/github.com/smira/aptly/swift/swift_test.go b/src/github.com/smira/aptly/swift/swift_test.go new file mode 100644 index 00000000..4ca59a2b --- /dev/null +++ b/src/github.com/smira/aptly/swift/swift_test.go @@ -0,0 +1,12 @@ +package swift + +import ( + "testing" + + . "gopkg.in/check.v1" +) + +// Launch gocheck tests +func Test(t *testing.T) { + TestingT(t) +} diff --git a/src/github.com/smira/aptly/system/api_lib.py b/src/github.com/smira/aptly/system/api_lib.py new file mode 100644 index 00000000..c62270c7 --- /dev/null +++ b/src/github.com/smira/aptly/system/api_lib.py @@ -0,0 +1,91 @@ +from lib import BaseTest +import time +import json +import random +import string +import os +import inspect +import shutil + +try: + import requests +except ImportError: + requests = None + + +class APITest(BaseTest): + """ + BaseTest + testing aptly API + """ + aptly_server = None + base_url = "127.0.0.1:8765" + + def fixture_available(self): + return super(APITest, self).fixture_available() and requests is not None + + def prepare(self): + if APITest.aptly_server is None: + super(APITest, self).prepare() + + APITest.aptly_server = self._start_process("aptly api serve -listen=%s" % (self.base_url),) + time.sleep(1) + + if os.path.exists(os.path.join(os.environ["HOME"], ".aptly", "upload")): + shutil.rmtree(os.path.join(os.environ["HOME"], ".aptly", "upload")) + + def run(self): + pass + + def get(self, uri, *args, **kwargs): + return requests.get("http://%s%s" % (self.base_url, uri), *args, **kwargs) + + def post(self, uri, *args, **kwargs): + if "json" in kwargs: + kwargs["data"] = json.dumps(kwargs.pop("json")) + if not "headers" in kwargs: + kwargs["headers"] = {} + kwargs["headers"]["Content-Type"] = "application/json" + return requests.post("http://%s%s" % (self.base_url, uri), *args, **kwargs) + + def put(self, uri, *args, **kwargs): + if "json" in kwargs: + kwargs["data"] = json.dumps(kwargs.pop("json")) + if not "headers" in kwargs: + kwargs["headers"] = {} + kwargs["headers"]["Content-Type"] = "application/json" + return requests.put("http://%s%s" % (self.base_url, uri), *args, **kwargs) + + def delete(self, uri, *args, **kwargs): + if "json" in kwargs: + kwargs["data"] = json.dumps(kwargs.pop("json")) + if not "headers" in kwargs: + kwargs["headers"] = {} + kwargs["headers"]["Content-Type"] = "application/json" + return requests.delete("http://%s%s" % (self.base_url, uri), *args, **kwargs) + + def upload(self, uri, *filenames, **kwargs): + upload_name = kwargs.pop("upload_name", None) + directory = kwargs.pop("directory", "files") + assert kwargs == {} + + files = {} + + for filename in filenames: + fp = open(os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), directory, filename), "rb") + if upload_name is not None: + upload_filename = upload_name + else: + upload_filename = filename + files[upload_filename] = (upload_filename, fp) + + return self.post(uri, files=files) + + @classmethod + def shutdown_class(cls): + if cls.aptly_server is not None: + cls.aptly_server.terminate() + cls.aptly_server.wait() + cls.aptly_server = None + + def random_name(self): + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(15)) diff --git a/src/github.com/smira/aptly/system/lib.py b/src/github.com/smira/aptly/system/lib.py index e3e84f6a..b7fccd36 100644 --- a/src/github.com/smira/aptly/system/lib.py +++ b/src/github.com/smira/aptly/system/lib.py @@ -14,6 +14,7 @@ import string import threading import urllib #import time +import pprint import SocketServer import SimpleHTTPServer @@ -149,25 +150,29 @@ class BaseTest(object): def run(self): self.output = self.output_processor(self.run_cmd(self.runCmd, self.expectedCode)) + def _start_process(self, command, stderr=subprocess.STDOUT, stdout=None): + if not hasattr(command, "__iter__"): + params = { + 'files': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files"), + 'udebs': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "udebs"), + 'testfiles': os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.__class__.__name__), + 'aptlyroot': os.path.join(os.environ["HOME"], ".aptly"), + } + if self.fixtureWebServer: + params['url'] = self.webServerUrl + + command = string.Template(command).substitute(params) + + command = shlex.split(command) + environ = os.environ.copy() + environ["LC_ALL"] = "C" + environ.update(self.environmentOverride) + return subprocess.Popen(command, stderr=stderr, stdout=stdout, env=environ) + def run_cmd(self, command, expected_code=0): try: #start = time.time() - if not hasattr(command, "__iter__"): - params = { - 'files': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files"), - 'udebs': os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "udebs"), - 'testfiles': os.path.join(os.path.dirname(inspect.getsourcefile(self.__class__)), self.__class__.__name__), - } - if self.fixtureWebServer: - params['url'] = self.webServerUrl - - command = string.Template(command).substitute(params) - - command = shlex.split(command) - environ = os.environ.copy() - environ["LC_ALL"] = "C" - environ.update(self.environmentOverride) - proc = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=environ) + proc = self._start_process(command, stdout=subprocess.PIPE) output, _ = proc.communicate() #print "CMD %s: %.2f" % (" ".join(command), time.time()-start) if proc.returncode != expected_code: @@ -254,6 +259,28 @@ class BaseTest(object): if os.path.exists(os.path.join(os.environ["HOME"], ".aptly", path)): raise Exception("path %s exists" % (path, )) + def check_file_not_empty(self, path): + if os.stat(os.path.join(os.environ["HOME"], ".aptly", path))[6] == 0: + raise Exception("file %s is empty" % (path, )) + + def check_equal(self, a, b): + if a != b: + self.verify_match(a, b, match_prepare=pprint.pformat) + + def check_in(self, item, l): + if not item in l: + raise Exception("item %r not in %r", item, l) + + def check_subset(self, a, b): + diff = '' + for k, v in a.items(): + if k not in b: + diff += "unexpected key '%s'\n" % (k,) + elif b[k] != v: + diff += "wrong value '%s' for key '%s', expected '%s'\n" % (v, k, b[k]) + if diff: + raise Exception("content doesn't match:\n" + diff) + def verify_match(self, a, b, match_prepare=None): if match_prepare is not None: a = match_prepare(a) @@ -287,3 +314,7 @@ class BaseTest(object): def shutdown_webserver(self): self.webserver.shutdown() + + @classmethod + def shutdown_class(cls): + pass diff --git a/src/github.com/smira/aptly/system/run.py b/src/github.com/smira/aptly/system/run.py index 8f8819c9..1c9ca581 100755 --- a/src/github.com/smira/aptly/system/run.py +++ b/src/github.com/smira/aptly/system/run.py @@ -6,9 +6,12 @@ import os import inspect import fnmatch import sys +import traceback from lib import BaseTest from s3_lib import S3Test +from swift_lib import SwiftTest +from api_lib import APITest try: from termcolor import colored @@ -25,6 +28,7 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non tests = glob.glob("t*_*") fails = [] numTests = numFailed = numSkipped = 0 + lastBase = None for test in tests: @@ -34,9 +38,15 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non o = getattr(testModule, name) if not (inspect.isclass(o) and issubclass(o, BaseTest) and o is not BaseTest and - o is not S3Test): + o is not SwiftTest and o is not S3Test and o is not APITest): continue + newBase = o.__bases__[0] + if lastBase is not None and lastBase is not newBase: + lastBase.shutdown_class() + + lastBase = newBase + if filters: matches = False @@ -60,23 +70,28 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non try: t.captureResults = capture_results t.test() - except BaseException, e: + except BaseException: numFailed += 1 - fails.append((test, t, e, testModule)) + typ, val, tb = sys.exc_info() + fails.append((test, t, typ, val, tb, testModule)) sys.stdout.write(colored("FAIL\n", color="red")) else: sys.stdout.write(colored("OK\n", color="green")) t.shutdown() + if lastBase is not None: + lastBase.shutdown_class() + print "TESTS: %d SUCCESS: %d FAIL: %d SKIP: %d" % (numTests, numTests - numFailed, numFailed, numSkipped) if len(fails) > 0: print "\nFAILURES (%d):" % (len(fails), ) - for (test, t, e, testModule) in fails: + for (test, t, typ, val, tb, testModule) in fails: print "%s:%s %s" % (test, t.__class__.__name__, testModule.__doc__.strip() + ": " + t.__doc__.strip()) - print "ERROR: %s" % (e, ) + #print "ERROR: %s" % (val, ) + traceback.print_exception(typ, val, tb) print "=" * 60 sys.exit(1) diff --git a/src/github.com/smira/aptly/system/s3_lib.py b/src/github.com/smira/aptly/system/s3_lib.py index 82f11be4..a2b43d22 100644 --- a/src/github.com/smira/aptly/system/s3_lib.py +++ b/src/github.com/smira/aptly/system/s3_lib.py @@ -24,12 +24,12 @@ class S3Test(BaseTest): def prepare(self): self.bucket_name = "aptly-sys-test-" + str(uuid.uuid4()) self.bucket = s3_conn.create_bucket(self.bucket_name) - self.configOverride["S3PublishEndpoints"] = { + self.configOverride = {"S3PublishEndpoints": { "test1": { "region": "us-east-1", "bucket": self.bucket_name, } - } + }} super(S3Test, self).prepare() diff --git a/src/github.com/smira/aptly/system/swift_lib.py b/src/github.com/smira/aptly/system/swift_lib.py new file mode 100644 index 00000000..5b2f05db --- /dev/null +++ b/src/github.com/smira/aptly/system/swift_lib.py @@ -0,0 +1,87 @@ +from lib import BaseTest +import uuid +import os + +try: + import swiftclient + + if 'OS_USERNAME' in os.environ and 'OS_PASSWORD' in os.environ: + auth_username = os.environ.get('OS_USERNAME') + auth_password = os.environ.get('OS_PASSWORD') + # Using auth version 2 /v2.0/ + auth_url = os.environ.get('OS_AUTH_URL') + auth_tenant = os.environ.get('OS_TENANT_NAME') + + account_username = "%s:%s" % (auth_tenant, auth_username) + swift_conn = swiftclient.Connection(auth_url, account_username, + auth_password, auth_version=2) + elif 'ST_USER' in os.environ and 'ST_KEY' in os.environ: + auth_username = os.environ.get('ST_USER') + auth_password = os.environ.get('ST_KEY') + auth_url = os.environ.get('ST_AUTH') + # Using auth version 1 (/auth/v1.0) + swift_conn = swiftclient.Connection(auth_url, auth_username, + auth_password, auth_version=1) + else: + swift_conn = None +except ImportError: + swift_conn = None + + +class SwiftTest(BaseTest): + """ + BaseTest + support for Swift + """ + + def fixture_available(self): + return super(SwiftTest, self).fixture_available() and swift_conn is not None + + def prepare(self): + self.container_name = "aptly-sys-test-" + str(uuid.uuid4()) + swift_conn.put_container(self.container_name) + + self.configOverride = {"SwiftPublishEndpoints": { + "test1": { + "container": self.container_name, + } + }} + + super(SwiftTest, self).prepare() + + def shutdown(self): + if hasattr(self, "container_name"): + for obj in swift_conn.get_container(self.container_name, + full_listing=True)[1]: + swift_conn.delete_object(self.container_name, obj.get("name")) + + swift_conn.delete_container(self.container_name) + super(SwiftTest, self).shutdown() + + def check_path(self, path): + if not hasattr(self, "container_contents"): + self.container_contents = [obj.get('name') for obj in + swift_conn.get_container(self.container_name)[1]] + + if path in self.container_contents: + return True + + if not path.endswith("/"): + path = path + "/" + + for item in self.container_contents: + if item.startswith(path): + return True + + return False + + def check_exists(self, path): + if not self.check_path(path): + raise Exception("path %s doesn't exist" % (path, )) + + def check_not_exists(self, path): + if self.check_path(path): + raise Exception("path %s exists" % (path, )) + + def read_file(self, path): + hdrs, body = swift_conn.get_object(self.container_name, path) + return body diff --git a/src/github.com/smira/aptly/system/t01_version/VersionTest_gold b/src/github.com/smira/aptly/system/t01_version/VersionTest_gold index b2155618..3fdf462a 100644 --- a/src/github.com/smira/aptly/system/t01_version/VersionTest_gold +++ b/src/github.com/smira/aptly/system/t01_version/VersionTest_gold @@ -1 +1 @@ -aptly version: 0.8 +aptly version: 0.9.1 diff --git a/src/github.com/smira/aptly/system/t02_config/ConfigShowTest_gold b/src/github.com/smira/aptly/system/t02_config/ConfigShowTest_gold new file mode 100644 index 00000000..6b537b76 --- /dev/null +++ b/src/github.com/smira/aptly/system/t02_config/ConfigShowTest_gold @@ -0,0 +1,17 @@ +{ + "rootDir": "${HOME}/.aptly", + "downloadConcurrency": 4, + "downloadSpeedLimit": 0, + "architectures": [], + "dependencyFollowSuggests": false, + "dependencyFollowRecommends": false, + "dependencyFollowAllVariants": false, + "dependencyFollowSource": false, + "gpgDisableSign": false, + "gpgDisableVerify": false, + "downloadSourcePackages": false, + "ppaDistributorID": "ubuntu", + "ppaCodename": "", + "S3PublishEndpoints": {}, + "SwiftPublishEndpoints": {} +} diff --git a/src/github.com/smira/aptly/system/t02_config/CreateConfigTest_gold b/src/github.com/smira/aptly/system/t02_config/CreateConfigTest_gold index 53c77185..1e827c19 100644 --- a/src/github.com/smira/aptly/system/t02_config/CreateConfigTest_gold +++ b/src/github.com/smira/aptly/system/t02_config/CreateConfigTest_gold @@ -12,5 +12,6 @@ "downloadSourcePackages": false, "ppaDistributorID": "ubuntu", "ppaCodename": "", - "S3PublishEndpoints": {} + "S3PublishEndpoints": {}, + "SwiftPublishEndpoints": {} } \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t02_config/__init__.py b/src/github.com/smira/aptly/system/t02_config/__init__.py index 380b63c2..9d5120ee 100644 --- a/src/github.com/smira/aptly/system/t02_config/__init__.py +++ b/src/github.com/smira/aptly/system/t02_config/__init__.py @@ -55,3 +55,11 @@ class ConfigInMissingFileTest(BaseTest): runCmd = ["aptly", "mirror", "list", "-config=nosuchfile.conf"] expectedCode = 1 prepare = BaseTest.prepare_remove_all + + +class ConfigShowTest(BaseTest): + """ + config showing + """ + runCmd = ["aptly", "config", "show"] + gold_processor = BaseTest.expand_environ diff --git a/src/github.com/smira/aptly/system/t03_help/MainTest_gold b/src/github.com/smira/aptly/system/t03_help/MainTest_gold index b468f99b..3339c975 100644 --- a/src/github.com/smira/aptly/system/t03_help/MainTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MainTest_gold @@ -2,6 +2,8 @@ aptly - Debian repository management tool Commands: + api start API server/issue requests + config manage aptly configuration db manage aptly's internal database and package pool graph render graph of relationships mirror manage mirrors of remote repositories @@ -10,6 +12,7 @@ Commands: repo manage local package repositories serve HTTP serve published repositories snapshot manage snapshots of repositories + task manage aptly tasks version display version Use "aptly help " for more information about a command. diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold index 9a37486e..e404e2d9 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorCreateHelpTest_gold @@ -21,6 +21,7 @@ Options: -dep-follow-suggests=false: when processing dependencies, follow Suggests -filter="": filter packages in mirror -filter-with-deps=false: when filtering, include dependencies of matching packages as well + -force-components=false: (only with component list) skip check that requested components are listed in Release file -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages diff --git a/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold b/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold index d696eb5d..2aaa8203 100644 --- a/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/MirrorCreateTest_gold @@ -12,6 +12,7 @@ Options: -dep-follow-suggests=false: when processing dependencies, follow Suggests -filter="": filter packages in mirror -filter-with-deps=false: when filtering, include dependencies of matching packages as well + -force-components=false: (only with component list) skip check that requested components are listed in Release file -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages diff --git a/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold b/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold index a6df4f0e..29c7a540 100644 --- a/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold +++ b/src/github.com/smira/aptly/system/t03_help/WrongFlagTest_gold @@ -13,6 +13,7 @@ Options: -dep-follow-suggests=false: when processing dependencies, follow Suggests -filter="": filter packages in mirror -filter-with-deps=false: when filtering, include dependencies of matching packages as well + -force-components=false: (only with component list) skip check that requested components are listed in Release file -ignore-signatures=false: disable verification of Release file signatures -keyring=: gpg keyring to use when verifying Release file (could be specified multiple times) -with-sources=false: download source packages in addition to binary packages diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show index 1a012184..5483fead 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror13Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show index 816e61d5..32815bb5 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror17Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show index 94f4085f..f2816c1a 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror1Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show index 385432d3..4c26c21e 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror21Test_mirror_show @@ -9,6 +9,6 @@ Last update: never Information from release file: Architectures: all -Date: Wed, 01 Oct 2014 18:30:34 UTC +Date: Wed, 28 Jan 2015 02:32:16 UTC Origin: jenkins-ci.org Suite: binary diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show index 7f5a17eb..fccbfcaa 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror25Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_gold new file mode 100644 index 00000000..2fe4f9ac --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_gold @@ -0,0 +1,4 @@ +Downloading http://linux.dell.com/repo/community/ubuntu/dists/wheezy/Release... + +Mirror [mirror27]: http://linux.dell.com/repo/community/ubuntu/ wheezy successfully added. +You can run 'aptly mirror update mirror27' to download repository contents. diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_mirror_show new file mode 100644 index 00000000..41412dd9 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror27Test_mirror_show @@ -0,0 +1,19 @@ +Name: mirror27 +Archive Root URL: http://linux.dell.com/repo/community/ubuntu/ +Distribution: wheezy +Components: openmanage/740 +Architectures: amd64, i386 +Download Sources: no +Download .udebs: no +Last update: never + +Information from release file: +Architectures: amd64 i386 source +Codename: wheezy +Components: openmanage openmanage/801 openmanage/740 openmanage/730 +Date: Wed, 22 Oct 2014 18:50:39 UTC +Description: Unofficial Dell OMSA build for Ubuntu + +Label: Dell OMSA Archive +Origin: Dell Inc +Suite: wheezy diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_gold new file mode 100644 index 00000000..34c7909d --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_gold @@ -0,0 +1,4 @@ +Downloading http://downloads-distro.mongodb.org/repo/ubuntu-upstart/dists/dist/Release... + +Mirror [mirror28]: http://downloads-distro.mongodb.org/repo/ubuntu-upstart/ dist successfully added. +You can run 'aptly mirror update mirror28' to download repository contents. diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_mirror_show new file mode 100644 index 00000000..bc9cc681 --- /dev/null +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror28Test_mirror_show @@ -0,0 +1,20 @@ +Name: mirror28 +Archive Root URL: http://downloads-distro.mongodb.org/repo/ubuntu-upstart/ +Distribution: dist +Components: 10gen +Architectures: amd64, i386 +Download Sources: no +Download .udebs: no +Last update: never + +Information from release file: +Architectures: i386 amd64 +Codename: dist +Components: mongodb +Date: Wed, 25 Feb 2015 17:35:02 UTC +Description: mongodb packages + +Label: mongodb +Origin: mongodb +Suite: mongodb +Version: dist diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show index 60c7d9de..c7f66fc1 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror2Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show index 5586b8b2..82d4be81 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror3Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror4Test_gold b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror4Test_gold index 815accb2..346ba83b 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror4Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror4Test_gold @@ -1,2 +1,2 @@ Downloading http://mirror.yandex.ru/debian/dists/wheezy/Release... -ERROR: unable to fetch mirror: component life not available in repo [mirror4]: http://mirror.yandex.ru/debian/ wheezy +ERROR: unable to fetch mirror: component life not available in repo [mirror4]: http://mirror.yandex.ru/debian/ wheezy, use -force-components to override diff --git a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show index 9f0029af..df88e3c3 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/CreateMirror7Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show index 3f448097..09ab3750 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show +++ b/src/github.com/smira/aptly/system/t04_mirror/EditMirror6Test_mirror_show @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold index 94f4085f..f2816c1a 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/ShowMirror1Test_gold @@ -11,10 +11,10 @@ Information from release file: Architectures: amd64 armel armhf i386 ia64 kfreebsd-amd64 kfreebsd-i386 mips mipsel powerpc s390 s390x sparc Codename: wheezy Components: main contrib non-free -Date: Sat, 12 Jul 2014 10:59:25 UTC -Description: Debian 7.6 Released 12 July 2014 +Date: Sat, 10 Jan 2015 11:18:41 UTC +Description: Debian 7.8 Released 10 January 2015 Label: Debian Origin: Debian Suite: stable -Version: 7.6 +Version: 7.8 diff --git a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold index 9d4793a6..a00804fe 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror11Test_gold @@ -14,7 +14,7 @@ Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysv-rc_2.88dsf- Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysvinit-utils_2.88dsf-41+deb7u1_i386.deb... Downloading ftp://ftp.ru.debian.org/debian/pool/main/s/sysvinit/sysvinit_2.88dsf-41+deb7u1_i386.deb... Mirror `wheezy-main` has been successfully updated. -Packages filtered: 36046 -> 5. +Packages filtered: 36038 -> 5. gpgv: Good signature from "Debian Archive Automatic Signing Key (7.0/wheezy) " gpgv: Good signature from "Wheezy Stable Release Key " gpgv: RSA key ID 46925553 diff --git a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror1Test_gold b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror1Test_gold index b880f152..fdb2cdcf 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror1Test_gold +++ b/src/github.com/smira/aptly/system/t04_mirror/UpdateMirror1Test_gold @@ -1,39 +1,51 @@ Building download queue... -Download queue: 30 items (11.70 MiB) +Download queue: 42 items (15.95 MiB) Downloading & parsing package files... Downloading http://repo.varnish-cache.org/debian/dists/wheezy/Release... Downloading http://repo.varnish-cache.org/debian/dists/wheezy/varnish-3.0/binary-amd64/Packages.bz2... Downloading http://repo.varnish-cache.org/debian/dists/wheezy/varnish-3.0/binary-i386/Packages.bz2... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_1.16.0~wheezy_all.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_2.2.0~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_2.2.0~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_2.2.1+nmu1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_2.2.1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_2.2.1~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish-agent/varnish-agent_3.0.0~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.3-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.3-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.4-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.4-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.5-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.5-1~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.6-1~wheezy_amd64.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi-dev_3.0.6-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.3-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.3-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.4-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.4-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.5-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.5-1~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.6-1~wheezy_amd64.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/libvarnishapi1_3.0.6-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.3-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.3-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.4-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.4-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.5-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.5-1~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.6-1~wheezy_amd64.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-dbg_3.0.6-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-doc_3.0.4-1~wheezy_all.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-doc_3.0.5-1~wheezy_all.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish-doc_3.0.6-1~wheezy_all.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.3-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.3-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.4-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.4-1~wheezy_i386.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.5-1~wheezy_amd64.deb... Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.5-1~wheezy_i386.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.6-1~wheezy_amd64.deb... +Downloading http://repo.varnish-cache.org/debian/pool/varnish-3.0/v/varnish/varnish_3.0.6-1~wheezy_i386.deb... Mirror `varnish` has been successfully updated. \ No newline at end of file diff --git a/src/github.com/smira/aptly/system/t04_mirror/create.py b/src/github.com/smira/aptly/system/t04_mirror/create.py index 0e469e33..77986642 100644 --- a/src/github.com/smira/aptly/system/t04_mirror/create.py +++ b/src/github.com/smira/aptly/system/t04_mirror/create.py @@ -249,8 +249,11 @@ class CreateMirror21Test(BaseTest): def removeSHA512(s): return re.sub(r"SHA512: .+\n", "", s) + def removeDates(s): + return re.sub(r"(Date|Valid-Until): [,0-9:+A-Za-z -]+\n", "", s) + self.check_output() - self.check_cmd_output("aptly mirror show mirror21", "mirror_show", match_prepare=removeSHA512) + self.check_cmd_output("aptly mirror show mirror21", "mirror_show", match_prepare=lambda s: removeSHA512(removeDates(s))) class CreateMirror22Test(BaseTest): @@ -306,3 +309,28 @@ class CreateMirror26Test(BaseTest): runCmd = "aptly mirror create -keyring=aptlytest.gpg -with-udebs mirror26 http://pkg.jenkins-ci.org/debian-stable binary/" fixtureGpg = True expectedCode = 1 + + +class CreateMirror27Test(BaseTest): + """ + create mirror: component with slashes, no stripping + """ + runCmd = "aptly mirror create --ignore-signatures mirror27 http://linux.dell.com/repo/community/ubuntu wheezy openmanage/740" + + def check(self): + self.check_output() + self.check_cmd_output("aptly mirror show mirror27", "mirror_show") + + +class CreateMirror28Test(BaseTest): + """ + create mirror: -force-components + """ + runCmd = "aptly mirror create -ignore-signatures -force-components mirror28 http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" + + def check(self): + def removeDates(s): + return re.sub(r"(Date|Valid-Until): [,0-9:+A-Za-z -]+\n", "", s) + + self.check_output() + self.check_cmd_output("aptly mirror show mirror28", "mirror_show", match_prepare=removeDates) diff --git a/src/github.com/smira/aptly/system/t05_snapshot/ListSnapshot7Test_gold b/src/github.com/smira/aptly/system/t05_snapshot/ListSnapshot7Test_gold index a9dfcb04..c706052d 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/ListSnapshot7Test_gold +++ b/src/github.com/smira/aptly/system/t05_snapshot/ListSnapshot7Test_gold @@ -1 +1,2 @@ +List of snapshots: ERROR: sorting method "planet" unknown diff --git a/src/github.com/smira/aptly/system/t05_snapshot/list.py b/src/github.com/smira/aptly/system/t05_snapshot/list.py index d3e576d0..49bd1f6a 100644 --- a/src/github.com/smira/aptly/system/t05_snapshot/list.py +++ b/src/github.com/smira/aptly/system/t05_snapshot/list.py @@ -87,5 +87,8 @@ class ListSnapshot7Test(BaseTest): """ list snapshots: wrong parameter sort """ + fixtureCmds = [ + "aptly snapshot create empty empty" + ] runCmd = "aptly -sort=planet snapshot list" expectedCode = 1 diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_release index dfeaa969..8e0cd9b6 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo12Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_release index 3a7b34c2..9c016172 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo15Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: label15 +Suite: maverick Codename: maverick Architectures: i386 Components: contrib diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_release index 0dcf1438..2f4a5f54 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo17Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: i386 Components: contrib main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_release index dfeaa969..8e0cd9b6 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishRepo1Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_release index 9444c59b..65d09a12 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot13Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_release index 9444c59b..65d09a12 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot15Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_release index 9444c59b..65d09a12 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot16Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_release index dfeaa969..8e0cd9b6 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot17Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 index fe39b007..96cabae2 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_amd64 @@ -1,10 +1,16 @@ Package: gnuplot -Version: 4.6.1-1~maverick2 -Installed-Size: 20 Priority: optional Section: math +Installed-Size: 20 Maintainer: Debian Science Team Architecture: all +Version: 4.6.1-1~maverick2 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Size: 1046 +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -17,21 +23,20 @@ Description: Command-line driven interactive plotting program . This package is for transition and to install a full-featured gnuplot supporting the X11-output. -MD5sum: 4912a4464d5588f685c4aa6cfc6be46c -SHA1: 4a50deb413e05f77b31687405465b1229b3be328 -Source: -Size: 1046 -Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb -Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) Package: gnuplot-doc -Version: 4.6.1-1~maverick2 -Installed-Size: 5572 Priority: optional Section: doc +Installed-Size: 5572 Maintainer: Debian Science Team Architecture: all +Source: gnuplot +Version: 4.6.1-1~maverick2 +Depends: dpkg (>= 1.15.4) | install-info +Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Size: 2675242 +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -43,20 +48,23 @@ Description: Command-line driven interactive plotting program and can work with complex numbers. . This package contains the additional documentation. -MD5sum: 25a5028811171f2f1fa157a2f6953e82 -SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 -Source: gnuplot -Depends: dpkg (>= 1.15.4) | install-info -Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb -Size: 2675242 Package: gnuplot-nox -Version: 4.6.1-1~maverick2 -Installed-Size: 2624 Priority: optional Section: math +Installed-Size: 2624 Maintainer: Debian Science Team Architecture: amd64 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) +Recommends: groff, ttf-liberation +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb +Size: 1129114 +MD5sum: db55daca818697b23024255e536399da +SHA1: d5a1b0bbfb562e5cecef3f3fb70ddb4cd6103507 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -68,23 +76,21 @@ Description: Command-line driven interactive plotting program and can work with complex numbers. . This package is for working without an X server. -MD5sum: db55daca818697b23024255e536399da -SHA1: d5a1b0bbfb562e5cecef3f3fb70ddb4cd6103507 -Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) -Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb -Replaces: gnuplot (<< 4.0.0) -Source: gnuplot -Recommends: groff, ttf-liberation -Size: 1129114 -Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) Package: gnuplot-x11 -Version: 4.6.1-1~maverick2 -Installed-Size: 1716 Priority: optional Section: math +Installed-Size: 1716 Maintainer: Debian Science Team Architecture: amd64 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb +Size: 819248 +MD5sum: 17ab6787992b979e3a4851a90dfaf0a8 +SHA1: d60b0ee30a885ba0202adddccd7968ab70be7426 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -99,11 +105,4 @@ Description: Command-line driven interactive plotting program images interactively under X11. Most users will want this, it is however packaged separately so that low-end systems don't need X installed to use gnuplot. -MD5sum: 17ab6787992b979e3a4851a90dfaf0a8 -SHA1: d60b0ee30a885ba0202adddccd7968ab70be7426 -Size: 819248 -Source: gnuplot -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 -Replaces: gnuplot (<< 4.0.0) -Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 index 1c5127de..d11a06e5 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_packages_i386 @@ -1,10 +1,16 @@ Package: gnuplot -Version: 4.6.1-1~maverick2 -Installed-Size: 20 Priority: optional Section: math +Installed-Size: 20 Maintainer: Debian Science Team Architecture: all +Version: 4.6.1-1~maverick2 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Size: 1046 +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -17,21 +23,20 @@ Description: Command-line driven interactive plotting program . This package is for transition and to install a full-featured gnuplot supporting the X11-output. -MD5sum: 4912a4464d5588f685c4aa6cfc6be46c -SHA1: 4a50deb413e05f77b31687405465b1229b3be328 -Size: 1046 -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) -Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) -Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb -Source: Package: gnuplot-doc -Version: 4.6.1-1~maverick2 -Installed-Size: 5572 Priority: optional Section: doc +Installed-Size: 5572 Maintainer: Debian Science Team Architecture: all +Source: gnuplot +Version: 4.6.1-1~maverick2 +Depends: dpkg (>= 1.15.4) | install-info +Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Size: 2675242 +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -43,20 +48,23 @@ Description: Command-line driven interactive plotting program and can work with complex numbers. . This package contains the additional documentation. -MD5sum: 25a5028811171f2f1fa157a2f6953e82 -SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 -Source: gnuplot -Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb -Depends: dpkg (>= 1.15.4) | install-info -Size: 2675242 Package: gnuplot-nox -Version: 4.6.1-1~maverick2 -Installed-Size: 2536 Priority: optional Section: math +Installed-Size: 2536 Maintainer: Debian Science Team Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) +Recommends: groff, ttf-liberation +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb +Size: 1046496 +MD5sum: a7ef16004b62fd78acb77edb058ea1c1 +SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -68,23 +76,21 @@ Description: Command-line driven interactive plotting program and can work with complex numbers. . This package is for working without an X server. -MD5sum: a7ef16004b62fd78acb77edb058ea1c1 -SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d -Size: 1046496 -Source: gnuplot -Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) -Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) -Recommends: groff, ttf-liberation -Replaces: gnuplot (<< 4.0.0) -Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb Package: gnuplot-x11 -Version: 4.6.1-1~maverick2 -Installed-Size: 1604 Priority: optional Section: math +Installed-Size: 1604 Maintainer: Debian Science Team Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb +Size: 724388 +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 Description: Command-line driven interactive plotting program Gnuplot is a portable command-line driven interactive data and function plotting utility that supports lots of output formats, including drivers @@ -99,11 +105,4 @@ Description: Command-line driven interactive plotting program images interactively under X11. Most users will want this, it is however packaged separately so that low-end systems don't need X installed to use gnuplot. -MD5sum: fcad938905d0ace50a6ce0c73b2c6583 -SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 -Source: gnuplot -Replaces: gnuplot (<< 4.0.0) -Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 -Size: 724388 diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release index 76006ae6..834b90ed 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Date: Fri, 31 Jan 2014 14:18:52 UTC Architectures: amd64 i386 diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_amd64 index 4cb8083a..540b0bdc 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_amd64 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_amd64 @@ -1,5 +1,5 @@ Origin: . maverick Label: . maverick -Architecture: amd64 Archive: maverick +Architecture: amd64 Component: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_i386 index 755768aa..48a4fa32 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_i386 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot1Test_release_i386 @@ -1,5 +1,5 @@ Origin: . maverick Label: . maverick -Architecture: i386 Archive: maverick +Architecture: i386 Component: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_release index a65af0db..bc1969d9 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot24Test_release @@ -1,5 +1,6 @@ Origin: aptly24 Label: . squeeze +Suite: squeeze Codename: squeeze Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_release index 5feb032f..b6b333b8 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot26Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: contrib main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_release index 4bbc2f0c..1dc41a3b 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot2Test_release @@ -1,5 +1,6 @@ Origin: . squeeze Label: . squeeze +Suite: squeeze Codename: squeeze Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 index 32ad0b4b..3ee55f9f 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_amd64 @@ -1,10 +1,16 @@ Package: dmraid -Version: 1.0.0.rc16-4.1 -Installed-Size: 112 Priority: optional Section: admin +Installed-Size: 112 Maintainer: Giuseppe Iuculano Architecture: amd64 +Version: 1.0.0.rc16-4.1 +Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup +Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_amd64.deb +Size: 38620 +MD5sum: 35da9bcdd12c7fb08eb7192f0a17ddf2 +SHA1: 6a89d3f9e3b80a172811bb7d74eac43f119a8b7c +SHA256: 125405c4b0a7364bf209c161f393d4d0152ba9d02a55a95d90a7637f7b373b8f Description: Device-Mapper Software RAID support tool dmraid discovers, activates, deactivates and displays properties of software RAID sets (eg, ATARAID) and contained DOS partitions. @@ -23,23 +29,23 @@ Description: Device-Mapper Software RAID support tool . Please read the documentation in /usr/share/doc/dmraid BEFORE attempting any use of this software. Improper use can cause data loss! -MD5sum: 35da9bcdd12c7fb08eb7192f0a17ddf2 -SHA1: 6a89d3f9e3b80a172811bb7d74eac43f119a8b7c -SHA256: 125405c4b0a7364bf209c161f393d4d0152ba9d02a55a95d90a7637f7b373b8f -Tag: admin::filesystem, admin::kernel, hardware::storage, implemented-in::c, interface::commandline, role::program, scope::utility, use::scanning -Size: 38620 -Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ -Source: -Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_amd64.deb +Tag: admin::filesystem, admin::kernel, hardware::storage, implemented-in::c, interface::commandline, role::program, scope::utility, use::scanning Package: libdmraid-dev -Version: 1.0.0.rc16-4.1 -Installed-Size: 496 Priority: optional Section: libdevel +Installed-Size: 496 Maintainer: Giuseppe Iuculano Architecture: amd64 +Source: dmraid +Version: 1.0.0.rc16-4.1 +Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) +Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_amd64.deb +Size: 152618 +MD5sum: bb209b5796592d786c28844b949216dc +SHA1: cd8baba807fa92a88a265a044d821df8b677b5cb +SHA256: 081a48ad5372a941c35d41733da89a52cbe2d8f49032c2a4ef03148e4049615f Description: Device-Mapper Software RAID support tool - header files dmraid discovers, activates, deactivates and displays properties of software RAID sets (eg, ATARAID) and contained DOS partitions. @@ -49,23 +55,24 @@ Description: Device-Mapper Software RAID support tool - header files . This package contains the header files needed to link programs against dmraid. -MD5sum: bb209b5796592d786c28844b949216dc -SHA1: cd8baba807fa92a88a265a044d821df8b677b5cb -SHA256: 081a48ad5372a941c35d41733da89a52cbe2d8f49032c2a4ef03148e4049615f -Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ Tag: admin::hardware, devel::lang:c, devel::library, hardware::storage, implemented-in::c, qa::low-popcon, role::devel-lib, use::driver -Size: 152618 -Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) -Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_amd64.deb -Source: dmraid +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ Package: libdmraid1.0.0.rc16 -Version: 1.0.0.rc16-4.1 -Installed-Size: 244 Priority: optional Section: libs +Installed-Size: 244 Maintainer: Giuseppe Iuculano Architecture: amd64 +Source: dmraid +Version: 1.0.0.rc16-4.1 +Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) +Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_amd64.deb +Size: 108978 +MD5sum: a66d03bb1ddad78f879660ddedf86295 +SHA1: 6292936617c466e67a3148c66d0c27c068d055d3 +SHA256: 29f06bd3ae42e3380b356b69598be07724d178af35f2f1a64648c7f8ff85bef9 Description: Device-Mapper Software RAID support tool - shared library dmraid discovers, activates, deactivates and displays properties of software RAID sets (eg, ATARAID) and contained DOS partitions. @@ -75,14 +82,6 @@ Description: Device-Mapper Software RAID support tool - shared library . This package contains the dmraid shared library, which implements the back half of dmraid, including on-disk metadata formats. -MD5sum: a66d03bb1ddad78f879660ddedf86295 -SHA1: 6292936617c466e67a3148c66d0c27c068d055d3 -SHA256: 29f06bd3ae42e3380b356b69598be07724d178af35f2f1a64648c7f8ff85bef9 -Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ -Size: 108978 Tag: admin::hardware, admin::kernel, devel::lang:c, devel::library, hardware::storage, implemented-in::c, role::{devel-lib,kernel,shared-lib}, use::driver -Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_amd64.deb -Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) -Source: dmraid diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 index be1c153c..72ecc2f8 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_packages_i386 @@ -1,37 +1,16 @@ -Package: libdmraid1.0.0.rc16 -Version: 1.0.0.rc16-4.1 -Installed-Size: 268 -Priority: optional -Section: libs -Maintainer: Giuseppe Iuculano -Architecture: i386 -Description: Device-Mapper Software RAID support tool - shared library - dmraid discovers, activates, deactivates and displays properties - of software RAID sets (eg, ATARAID) and contained DOS partitions. - . - dmraid uses the Linux device-mapper to create devices with respective - mappings for the ATARAID sets discovered. - . - This package contains the dmraid shared library, which implements - the back half of dmraid, including on-disk metadata formats. -MD5sum: 9330ba2ffd2f22d695fdf692f8120159 -SHA1: 6b262419836e8cad4500043f5e9e6a1581074023 -SHA256: 2b2238679ac8ff4776a3a2caf533c551700d9f92a7d2af23d6457acf7de5d6c8 -Tag: admin::hardware, admin::kernel, devel::lang:c, devel::library, hardware::storage, implemented-in::c, role::{devel-lib,kernel,shared-lib}, use::driver -Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_i386.deb -Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) -Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ -Source: dmraid -Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) -Size: 106088 - Package: dmraid -Version: 1.0.0.rc16-4.1 -Installed-Size: 176 Priority: optional Section: admin +Installed-Size: 176 Maintainer: Giuseppe Iuculano Architecture: i386 +Version: 1.0.0.rc16-4.1 +Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup +Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_i386.deb +Size: 37984 +MD5sum: f8aea4e9eaea341b112f02e9efe1678e +SHA1: bb96a258038c79bc04eef49d5875deed4c67dd16 +SHA256: 6a8294bef99040055009da41597869bfdb17ac89c3166e49c57340abe7f702ba Description: Device-Mapper Software RAID support tool dmraid discovers, activates, deactivates and displays properties of software RAID sets (eg, ATARAID) and contained DOS partitions. @@ -50,23 +29,23 @@ Description: Device-Mapper Software RAID support tool . Please read the documentation in /usr/share/doc/dmraid BEFORE attempting any use of this software. Improper use can cause data loss! -MD5sum: f8aea4e9eaea341b112f02e9efe1678e -SHA1: bb96a258038c79bc04eef49d5875deed4c67dd16 -SHA256: 6a8294bef99040055009da41597869bfdb17ac89c3166e49c57340abe7f702ba -Size: 37984 -Depends: libc6 (>= 2.3), libdmraid1.0.0.rc16 (>= 1.0.0.rc16), libselinux1 (>= 1.32), libsepol1 (>= 1.14), udev, dmsetup -Filename: pool/main/d/dmraid/dmraid_1.0.0.rc16-4.1_i386.deb Tag: admin::filesystem, admin::kernel, hardware::storage, implemented-in::c, interface::commandline, role::program, scope::utility, use::scanning -Source: Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ Package: libdmraid-dev -Version: 1.0.0.rc16-4.1 -Installed-Size: 440 Priority: optional Section: libdevel +Installed-Size: 440 Maintainer: Giuseppe Iuculano Architecture: i386 +Source: dmraid +Version: 1.0.0.rc16-4.1 +Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) +Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_i386.deb +Size: 145808 +MD5sum: 5395970df02ab5f1609cd7eccc15ead1 +SHA1: f27bd38eeb58a32ee7e58ac8a2950649bd4ef17b +SHA256: 2abe9142ce6aa341df57303b5bc847522779ea9109b0fe734e2ae4419872da71 Description: Device-Mapper Software RAID support tool - header files dmraid discovers, activates, deactivates and displays properties of software RAID sets (eg, ATARAID) and contained DOS partitions. @@ -76,13 +55,33 @@ Description: Device-Mapper Software RAID support tool - header files . This package contains the header files needed to link programs against dmraid. -MD5sum: 5395970df02ab5f1609cd7eccc15ead1 -SHA1: f27bd38eeb58a32ee7e58ac8a2950649bd4ef17b -SHA256: 2abe9142ce6aa341df57303b5bc847522779ea9109b0fe734e2ae4419872da71 Tag: admin::hardware, devel::lang:c, devel::library, hardware::storage, implemented-in::c, qa::low-popcon, role::devel-lib, use::driver Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ -Size: 145808 -Filename: pool/main/d/dmraid/libdmraid-dev_1.0.0.rc16-4.1_i386.deb -Depends: libdmraid1.0.0.rc16 (= 1.0.0.rc16-4.1) -Source: dmraid + +Package: libdmraid1.0.0.rc16 +Priority: optional +Section: libs +Installed-Size: 268 +Maintainer: Giuseppe Iuculano +Architecture: i386 +Source: dmraid +Version: 1.0.0.rc16-4.1 +Replaces: libdmraid1.0.0.rc15 (<< 1.0.0.rc16-1) +Depends: libc6 (>= 2.7), libdevmapper1.02.1 (>= 2:1.02.20) +Filename: pool/main/d/dmraid/libdmraid1.0.0.rc16_1.0.0.rc16-4.1_i386.deb +Size: 106088 +MD5sum: 9330ba2ffd2f22d695fdf692f8120159 +SHA1: 6b262419836e8cad4500043f5e9e6a1581074023 +SHA256: 2b2238679ac8ff4776a3a2caf533c551700d9f92a7d2af23d6457acf7de5d6c8 +Description: Device-Mapper Software RAID support tool - shared library + dmraid discovers, activates, deactivates and displays properties + of software RAID sets (eg, ATARAID) and contained DOS partitions. + . + dmraid uses the Linux device-mapper to create devices with respective + mappings for the ATARAID sets discovered. + . + This package contains the dmraid shared library, which implements + the back half of dmraid, including on-disk metadata formats. +Tag: admin::hardware, admin::kernel, devel::lang:c, devel::library, hardware::storage, implemented-in::c, role::{devel-lib,kernel,shared-lib}, use::driver +Homepage: http://people.redhat.com/~heinzm/sw/dmraid/ diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release index 2e1afbf0..9936f356 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release @@ -1,5 +1,6 @@ Origin: . squeeze Label: . squeeze +Suite: squeeze Codename: squeeze Date: Tue, 30 Sep 2014 15:35:22 UTC Architectures: amd64 i386 diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 index 77d3c28c..b00dab38 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot35Test_release_udeb_i386 @@ -1,5 +1,5 @@ Origin: . squeeze Label: . squeeze -Architecture: i386 Archive: squeeze +Architecture: i386 Component: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_release index 2e61a552..f1aa99ac 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot3Test_release @@ -1,5 +1,6 @@ Origin: . squeeze Label: . squeeze +Suite: squeeze Codename: squeeze Architectures: amd64 i386 Components: contrib diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_release index 4141caef..960b26eb 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSnapshot4Test_release @@ -1,5 +1,6 @@ Origin: . squeeze Label: . squeeze +Suite: squeeze Codename: squeeze Architectures: i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch12Test_gold b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch12Test_gold new file mode 100644 index 00000000..2fb3bcac --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch12Test_gold @@ -0,0 +1 @@ +ERROR: unable to switch: component c is not in published repository diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_release index 9444c59b..65d09a12 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch1Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_binary b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_binary index 9224d25c..d11a06e5 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_binary +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch2Test_binary @@ -1,109 +1,108 @@ - - - - - . - . - . - . - . - . - . - . - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - This package contains the additional documentation. - This package contains the terminal driver that enables gnuplot to plot - This package is for transition and to install a full-featured gnuplot - This package is for working without an X server. - and can work with complex numbers. - and can work with complex numbers. - and can work with complex numbers. - and can work with complex numbers. - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - gnuplot. - images interactively under X11. Most users will want this, it is however - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - packaged separately so that low-end systems don't need X installed to use - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - supporting the X11-output. -Architecture: all -Architecture: all -Architecture: i386 -Architecture: i386 -Depends: dpkg (>= 1.15.4) | install-info -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 -Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb -Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb -Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb -Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb -Installed-Size: 1604 -Installed-Size: 20 -Installed-Size: 2536 -Installed-Size: 5572 -MD5sum: 25a5028811171f2f1fa157a2f6953e82 -MD5sum: 4912a4464d5588f685c4aa6cfc6be46c -MD5sum: a7ef16004b62fd78acb77edb058ea1c1 -MD5sum: fcad938905d0ace50a6ce0c73b2c6583 -Maintainer: Debian Science Team -Maintainer: Debian Science Team -Maintainer: Debian Science Team -Maintainer: Debian Science Team Package: gnuplot +Priority: optional +Section: math +Installed-Size: 20 +Maintainer: Debian Science Team +Architecture: all +Version: 4.6.1-1~maverick2 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Size: 1046 +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for transition and to install a full-featured gnuplot + supporting the X11-output. + Package: gnuplot-doc +Priority: optional +Section: doc +Installed-Size: 5572 +Maintainer: Debian Science Team +Architecture: all +Source: gnuplot +Version: 4.6.1-1~maverick2 +Depends: dpkg (>= 1.15.4) | install-info +Filename: pool/main/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Size: 2675242 +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the additional documentation. + Package: gnuplot-nox +Priority: optional +Section: math +Installed-Size: 2536 +Maintainer: Debian Science Team +Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) +Recommends: groff, ttf-liberation +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb +Size: 1046496 +MD5sum: a7ef16004b62fd78acb77edb058ea1c1 +SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for working without an X server. + Package: gnuplot-x11 Priority: optional -Priority: optional -Priority: optional -Priority: optional -Recommends: groff, ttf-liberation +Section: math +Installed-Size: 1604 +Maintainer: Debian Science Team +Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 Replaces: gnuplot (<< 4.0.0) -Replaces: gnuplot (<< 4.0.0) -SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 -SHA1: 4a50deb413e05f77b31687405465b1229b3be328 -SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d -SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 -Section: doc -Section: math -Section: math -Section: math -Size: 1046 -Size: 1046496 -Size: 2675242 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb Size: 724388 -Source: -Source: gnuplot -Source: gnuplot -Source: gnuplot -Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) -Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_binaryA b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_binaryA index 51c8d2dd..7f0a2f15 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_binaryA +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_binaryA @@ -1,109 +1,108 @@ - - - - - . - . - . - . - . - . - . - . - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Data files and self-defined functions can be manipulated by the internal - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - Gnuplot is a portable command-line driven interactive data and function - This package contains the additional documentation. - This package contains the terminal driver that enables gnuplot to plot - This package is for transition and to install a full-featured gnuplot - This package is for working without an X server. - and can work with complex numbers. - and can work with complex numbers. - and can work with complex numbers. - and can work with complex numbers. - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output - gnuplot. - images interactively under X11. Most users will want this, it is however - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - is packaged in gnuplot-x11. - packaged separately so that low-end systems don't need X installed to use - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - plotting utility that supports lots of output formats, including drivers - supporting the X11-output. -Architecture: all -Architecture: all -Architecture: i386 -Architecture: i386 -Depends: dpkg (>= 1.15.4) | install-info -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) -Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 -Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Description: Command-line driven interactive plotting program -Filename: pool/a/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb -Filename: pool/a/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb -Filename: pool/a/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb -Filename: pool/a/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb -Installed-Size: 1604 -Installed-Size: 20 -Installed-Size: 2536 -Installed-Size: 5572 -MD5sum: 25a5028811171f2f1fa157a2f6953e82 -MD5sum: 4912a4464d5588f685c4aa6cfc6be46c -MD5sum: a7ef16004b62fd78acb77edb058ea1c1 -MD5sum: fcad938905d0ace50a6ce0c73b2c6583 -Maintainer: Debian Science Team -Maintainer: Debian Science Team -Maintainer: Debian Science Team -Maintainer: Debian Science Team Package: gnuplot +Priority: optional +Section: math +Installed-Size: 20 +Maintainer: Debian Science Team +Architecture: all +Version: 4.6.1-1~maverick2 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), gnuplot-x11 (>= 4.6.1-1~maverick2) +Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/a/g/gnuplot/gnuplot_4.6.1-1~maverick2_all.deb +Size: 1046 +MD5sum: 4912a4464d5588f685c4aa6cfc6be46c +SHA1: 4a50deb413e05f77b31687405465b1229b3be328 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for transition and to install a full-featured gnuplot + supporting the X11-output. + Package: gnuplot-doc +Priority: optional +Section: doc +Installed-Size: 5572 +Maintainer: Debian Science Team +Architecture: all +Source: gnuplot +Version: 4.6.1-1~maverick2 +Depends: dpkg (>= 1.15.4) | install-info +Filename: pool/a/g/gnuplot/gnuplot-doc_4.6.1-1~maverick2_all.deb +Size: 2675242 +MD5sum: 25a5028811171f2f1fa157a2f6953e82 +SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the additional documentation. + Package: gnuplot-nox +Priority: optional +Section: math +Installed-Size: 2536 +Maintainer: Debian Science Team +Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 +Replaces: gnuplot (<< 4.0.0) +Depends: libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0) +Recommends: groff, ttf-liberation +Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) +Filename: pool/a/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb +Size: 1046496 +MD5sum: a7ef16004b62fd78acb77edb058ea1c1 +SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package is for working without an X server. + Package: gnuplot-x11 Priority: optional -Priority: optional -Priority: optional -Priority: optional -Recommends: groff, ttf-liberation +Section: math +Installed-Size: 1604 +Maintainer: Debian Science Team +Architecture: i386 +Source: gnuplot +Version: 4.6.1-1~maverick2 Replaces: gnuplot (<< 4.0.0) -Replaces: gnuplot (<< 4.0.0) -SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 -SHA1: 4a50deb413e05f77b31687405465b1229b3be328 -SHA1: 629c3e62f787b0af47b184beb0460dd261c9ca4d -SHA1: 837dd002143054ca01d3b01cae410cc4b4fe10c4 -Section: doc -Section: math -Section: math -Section: math -Size: 1046 -Size: 1046496 -Size: 2675242 +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Filename: pool/a/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb Size: 724388 -Source: -Source: gnuplot -Source: gnuplot -Source: gnuplot -Suggests: gnuplot-doc (>= 4.6.1-1~maverick2) -Suggests: gnuplot-x11 (>= 4.6.1-1~maverick2), gnuplot-doc (>= 4.6.1-1~maverick2) -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 -Version: 4.6.1-1~maverick2 +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. + diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_release index 7e428416..8f0e668a 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishSwitch8Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: amd64 i386 Components: a b c diff --git a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_release b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_release index dfeaa969..8e0cd9b6 100644 --- a/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/PublishUpdate1Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Architectures: i386 Components: main diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release index ad1fa6fd..797e1922 100644 --- a/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish1Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Date: Wed, 1 Oct 2014 08:48:48 UTC Architectures: i386 diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release index 7c89bc78..663c30cc 100644 --- a/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish2Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Date: Wed, 1 Oct 2014 09:13:14 UTC Architectures: i386 diff --git a/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release index 892aa912..4d429b28 100644 --- a/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release +++ b/src/github.com/smira/aptly/system/t06_publish/S3Publish3Test_release @@ -1,5 +1,6 @@ Origin: . maverick Label: . maverick +Suite: maverick Codename: maverick Date: Wed, 1 Oct 2014 09:16:49 UTC Architectures: amd64 i386 diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_binary b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_binary new file mode 100644 index 00000000..eecd933f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_binary @@ -0,0 +1,25 @@ +Package: libboost-program-options-dev +Version: 1.49.0.1 +Installed-Size: 26 +Priority: optional +Section: libdevel +Maintainer: Debian Boost Team +Architecture: i386 +Description: program options library for C++ (default version) + This package forms part of the Boost C++ Libraries collection. + . + Library to let program developers obtain program options, that is + (name, value) pairs from the user, via conventional methods such as + command line and config file. + . + This package is a dependency package, which depends on Debian's default + Boost version (currently 1.49). +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Size: 2738 +Homepage: http://www.boost.org/libs/program_options/ +Source: boost-defaults +Depends: libboost-program-options1.49-dev + diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_gold b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_gold new file mode 100644 index 00000000..b65701eb --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_gold @@ -0,0 +1,13 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: + +Local repo local-repo has been successfully published. +Now you can add following line to apt sources: + deb http://your-server/ maverick main + deb-src http://your-server/ maverick main +Don't forget to add your GPG key to apt with apt-key. + +You can also use `aptly serve` to publish your repositories over HTTP quickly. diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_release b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_release new file mode 100644 index 00000000..05463baf --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_release @@ -0,0 +1,35 @@ +Origin: . maverick +Label: . maverick +Suite: maverick +Codename: maverick +Date: Sun, 22 Feb 2015 10:44:27 UTC +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: + c002a2d87044f69fe3f535cd126db3ba 92 main/source/Release + c03724183668d701ac6aa300d13af562 984 main/binary-i386/Packages + 585addbc83368aeee669c9c4894aa85d 601 main/binary-i386/Packages.gz + c6622332526ac6c6b0e3945ed4977d92 650 main/binary-i386/Packages.bz2 + 43403263280667501151421b9cd7fc68 2300 main/source/Sources + 8f77bf834a8c9a30f5647b153d73dc88 828 main/source/Sources.gz + 30682c2ffbfcb8141af41fc88236ffcf 1013 main/source/Sources.bz2 + 2dea0f34410be57d2e41713f14dd047b 90 main/binary-i386/Release +SHA1: + a4d68060e2ba2b00073d2b8848224b30c81a2060 92 main/source/Release + 5baf9f7d7ece54603fe839a414cd652d22da6ba2 984 main/binary-i386/Packages + 312c9eb461c3bc08d26355f4e233ea761573fad4 601 main/binary-i386/Packages.gz + 7f5a42f1cccde76e61db4785cc7e2d568366527f 650 main/binary-i386/Packages.bz2 + 7faccfe9d57725a221f7bcf24cb9f6fd15cfbc46 2300 main/source/Sources + e779570785b5be8be647cecbe2f9aee9593e9f22 828 main/source/Sources.gz + 5cadb5837aae91aa666b76030fdd89db9142aeda 1013 main/source/Sources.bz2 + 689d7b3d67cb1cc3d4a0e730a2c2462aa3344fef 90 main/binary-i386/Release +SHA256: + 8fd77dcde4aacfdaca30f1c74ec058ffdb79ec741ec0c04647c90d927f74ced8 92 main/source/Release + e16e3432eaf9a48f782c2afc92263819d2295169d276635282a163cb4b8da073 984 main/binary-i386/Packages + 2ced8276178f296b76d85f2a2bd640d912923d0110b98c86899be1eac3f98afc 601 main/binary-i386/Packages.gz + 97fbdadc2dfc8d26c9f4ea11642e208a3abe5d6868ba49b5c855ccfb54311ae3 650 main/binary-i386/Packages.bz2 + bef12367ae59c9bd0e4dfd35ff1a5b202fa74d32bf842ff973096f5397912f3f 2300 main/source/Sources + 085e855df6237384010d4147b80f600b190045ee9018dcecd64fa9441218f306 828 main/source/Sources.gz + c0c27f19c08ff311efd4611990959866464bdcb6ef73fdd4904a93992301299c 1013 main/source/Sources.bz2 + faa23b79fc6811f4eb4839e2a98bb023c9d70b815b1f39b1e54b8c42a0afc74e 90 main/binary-i386/Release diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_sources b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_sources new file mode 100644 index 00000000..7ea3c88c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish1Test_sources @@ -0,0 +1,42 @@ +Package: pyspi +Version: 0.6.1-1.3 +Maintainer: Jose Carlos Garcia Sogo +Architecture: any +Binary: python-at-spi +Standards-Version: 3.7.3 +Format: 1.0 +Files: 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + b72cb94699298a117b7c82641c68b6fd 1782 pyspi_0.6.1-1.3.dsc + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz +Checksums-Sha1: 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 56c8a9b1f4ab636052be8966690998cbe865cd6c 1782 pyspi_0.6.1-1.3.dsc + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Homepage: http://people.redhat.com/zcerza/dogtail +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Directory: pool/main/p/pyspi +Checksums-Sha256: 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + d494aaf526f1ec6b02f14c2f81e060a5722d6532ddc760ec16972e45c2625989 1782 pyspi_0.6.1-1.3.dsc + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz + +Package: pyspi +Version: 0.6.1-1.4 +Maintainer: Jose Carlos Garcia Sogo +Architecture: any +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Standards-Version: 3.7.3 +Homepage: http://people.redhat.com/zcerza/dogtail +Directory: pool/main/p/pyspi +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Checksums-Sha256: 289d3aefa970876e9c43686ce2b02f478d7f3ed35a713928464a98d54ae4fca3 893 pyspi-0.6.1-1.3.stripped.dsc + 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz + 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz +Format: 1.0 +Checksums-Sha1: 5005fbd1f30637edc1d380b30f45db9b79100d07 893 pyspi-0.6.1-1.3.stripped.dsc + 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz + 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz +Binary: python-at-spi +Files: 2f5bd47cf38852b6fc927a50f98c1448 893 pyspi-0.6.1-1.3.stripped.dsc + 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz + def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz + diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_binary b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_binary new file mode 100644 index 00000000..05d63c09 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_binary @@ -0,0 +1,25 @@ +Package: libboost-program-options-dev +Version: 1.49.0.1 +Installed-Size: 26 +Priority: optional +Section: libdevel +Maintainer: Debian Boost Team +Architecture: i386 +Description: program options library for C++ (default version) + This package forms part of the Boost C++ Libraries collection. + . + Library to let program developers obtain program options, that is + (name, value) pairs from the user, via conventional methods such as + command line and config file. + . + This package is a dependency package, which depends on Debian's default + Boost version (currently 1.49). +MD5sum: 0035d7822b2f8f0ec4013f270fd650c2 +SHA1: 36895eb64cfe89c33c0a2f7ac2f0c6e0e889e04b +SHA256: c76b4bd12fd92e4dfe1b55b18a67a669d92f62985d6a96c8a21d96120982cf12 +Source: boost-defaults +Filename: pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb +Depends: libboost-program-options1.49-dev +Homepage: http://www.boost.org/libs/program_options/ +Size: 2738 + diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_gold b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_gold new file mode 100644 index 00000000..5beaf677 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_gold @@ -0,0 +1,8 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: +Cleaning up prefix "." components main... + +Publish for local repo swift:test1:./maverick [i386, source] publishes {main: [local-repo]} has been successfully updated. diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_release b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_release new file mode 100644 index 00000000..964df748 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_release @@ -0,0 +1,35 @@ +Origin: . maverick +Label: . maverick +Suite: maverick +Codename: maverick +Date: Sun, 22 Feb 2015 10:44:40 UTC +Architectures: i386 +Components: main +Description: Generated by aptly +MD5Sum: + 585addbc83368aeee669c9c4894aa85d 601 main/binary-i386/Packages.gz + c6622332526ac6c6b0e3945ed4977d92 650 main/binary-i386/Packages.bz2 + d41d8cd98f00b204e9800998ecf8427e 0 main/source/Sources + f41c10a4b35cd3e1ec8abb9c2ab676ed 23 main/source/Sources.gz + 4059d198768f9f8dc9372dc1c54bc3c3 14 main/source/Sources.bz2 + 2dea0f34410be57d2e41713f14dd047b 90 main/binary-i386/Release + c002a2d87044f69fe3f535cd126db3ba 92 main/source/Release + c03724183668d701ac6aa300d13af562 984 main/binary-i386/Packages +SHA1: + 312c9eb461c3bc08d26355f4e233ea761573fad4 601 main/binary-i386/Packages.gz + 7f5a42f1cccde76e61db4785cc7e2d568366527f 650 main/binary-i386/Packages.bz2 + da39a3ee5e6b4b0d3255bfef95601890afd80709 0 main/source/Sources + 92c6cff562771f64540523a54baaa0b2afe54b3f 23 main/source/Sources.gz + 64a543afbb5f4bf728636bdcbbe7a2ed0804adc2 14 main/source/Sources.bz2 + 689d7b3d67cb1cc3d4a0e730a2c2462aa3344fef 90 main/binary-i386/Release + a4d68060e2ba2b00073d2b8848224b30c81a2060 92 main/source/Release + 5baf9f7d7ece54603fe839a414cd652d22da6ba2 984 main/binary-i386/Packages +SHA256: + 2ced8276178f296b76d85f2a2bd640d912923d0110b98c86899be1eac3f98afc 601 main/binary-i386/Packages.gz + 97fbdadc2dfc8d26c9f4ea11642e208a3abe5d6868ba49b5c855ccfb54311ae3 650 main/binary-i386/Packages.bz2 + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/source/Sources + 1775fca35fb6a4d31c541746eaea63c5cb3c00280c8b5a351d4e944cdca7489d 23 main/source/Sources.gz + d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/source/Sources.bz2 + faa23b79fc6811f4eb4839e2a98bb023c9d70b815b1f39b1e54b8c42a0afc74e 90 main/binary-i386/Release + 8fd77dcde4aacfdaca30f1c74ec058ffdb79ec741ec0c04647c90d927f74ced8 92 main/source/Release + e16e3432eaf9a48f782c2afc92263819d2295169d276635282a163cb4b8da073 984 main/binary-i386/Packages diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_sources b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish2Test_sources new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_binary b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_binary new file mode 100644 index 00000000..68a4003f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_binary @@ -0,0 +1,29 @@ +Package: gnuplot-x11 +Version: 4.6.1-1~maverick2 +Installed-Size: 1604 +Priority: optional +Section: math +Maintainer: Debian Science Team +Architecture: i386 +Description: Command-line driven interactive plotting program + Gnuplot is a portable command-line driven interactive data and function + plotting utility that supports lots of output formats, including drivers + for many printers, (La)TeX, (x)fig, Postscript, and so on. The X11-output + is packaged in gnuplot-x11. + . + Data files and self-defined functions can be manipulated by the internal + C-like language. Can perform smoothing, spline-fitting, or nonlinear fits, + and can work with complex numbers. + . + This package contains the terminal driver that enables gnuplot to plot + images interactively under X11. Most users will want this, it is however + packaged separately so that low-end systems don't need X installed to use + gnuplot. +MD5sum: fcad938905d0ace50a6ce0c73b2c6583 +SHA1: 02f9a93097a8f798a054e26154dbe5789088c069 +Replaces: gnuplot (<< 4.0.0) +Filename: pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb +Depends: gnuplot-nox (>= 4.6.1-1~maverick2), libc6 (>= 2.11), libcairo2 (>= 1.6.0), libedit2 (>= 2.5.cvs.20010821-1), libgcc1 (>= 1:4.1.1), libgd2-noxpm (>= 2.0.36~rc1~dfsg) | libgd2-xpm (>= 2.0.36~rc1~dfsg), libglib2.0-0 (>= 2.12.0), liblua5.1-0, libpango1.0-0 (>= 1.14.0), libstdc++6 (>= 4.1.1), libwxbase2.8-0 (>= 2.8.11.0), libwxgtk2.8-0 (>= 2.8.11.0), libx11-6 +Size: 724388 +Source: gnuplot + diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_gold b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_gold new file mode 100644 index 00000000..4b493d05 --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_gold @@ -0,0 +1,8 @@ +Loading packages... +Generating metadata files and linking package files... +Finalizing metadata files... +Signing file 'Release' with gpg, please enter your passphrase when prompted: +Clearsigning file 'Release' with gpg, please enter your passphrase when prompted: +Cleaning up prefix "." components main... + +Publish for snapshot swift:test1:./maverick [amd64, i386] publishes {main: [snap3]: Pulled into 'snap2' with 'snap1' as source, pull request was: 'gnuplot-x11'} has been successfully switched to new snapshot. diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_release b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_release new file mode 100644 index 00000000..8e09f83a --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish3Test_release @@ -0,0 +1,35 @@ +Origin: . maverick +Label: . maverick +Suite: maverick +Codename: maverick +Date: Sun, 22 Feb 2015 10:45:11 UTC +Architectures: amd64 i386 +Components: main +Description: Generated by aptly +MD5Sum: + ed4a96d0034fa903b943468d1a7cb73b 91 main/binary-amd64/Release + 2dea0f34410be57d2e41713f14dd047b 90 main/binary-i386/Release + 9fe269c89d1aac6bceeef8d9df4b3735 1526 main/binary-amd64/Packages + a6f1ebab83ff467727220c85cb3fb093 863 main/binary-amd64/Packages.gz + 583f5b63ccd8f833789357440f414eaf 930 main/binary-amd64/Packages.bz2 + 515b376e5200e6e5c3d26b2bb3466123 1524 main/binary-i386/Packages + 9b03fafb2b578c5e678a85ffd2950084 865 main/binary-i386/Packages.gz + 548ab0b40975dc0e28a0f97421294c1d 929 main/binary-i386/Packages.bz2 +SHA1: + 95af074e15d6a50d8f5defdf5f8a0f464d62ce97 91 main/binary-amd64/Release + 689d7b3d67cb1cc3d4a0e730a2c2462aa3344fef 90 main/binary-i386/Release + f4d8cfac4c02f57be17a0888971a7af5d68fe58b 1526 main/binary-amd64/Packages + c0b190dc735a59e18db4a6db3cbe2f06615f0f6b 863 main/binary-amd64/Packages.gz + bdecced219abb1db32c0df390f19df5f8656f975 930 main/binary-amd64/Packages.bz2 + d96e51c75dc23ae516d10f27931d46a65ed136e2 1524 main/binary-i386/Packages + 8c6f4dc45c9870d5b73751ba336640220f70d4c8 865 main/binary-i386/Packages.gz + 16d49629b2fcc5eb3557fe4a712953d255ef2042 929 main/binary-i386/Packages.bz2 +SHA256: + 3d9f8a049c9f85b8755316b04240369f73e0b74a8e4e64d008b46116e47f656e 91 main/binary-amd64/Release + faa23b79fc6811f4eb4839e2a98bb023c9d70b815b1f39b1e54b8c42a0afc74e 90 main/binary-i386/Release + 7af97eb8a100b006cc2b49c9d12b4ed78e2ba89c05ff8c0059898c8eab9b1400 1526 main/binary-amd64/Packages + cc842568f69d941516414b5753e9c1f500bfe6a209ee2d0cece17554715eabdb 863 main/binary-amd64/Packages.gz + e1511b7ffc5f9bcc6e1fdcfb32e5a8902f339f28910776d771de8c0dcd10034e 930 main/binary-amd64/Packages.bz2 + 1c06ffbaae938cca6f1471c7074fbf1ae5da09033183a4e41d9d4737ddc19048 1524 main/binary-i386/Packages + e469b02604cec35f69912b375b948ff2190a8741cd74ca175d6baed7ba4ca280 865 main/binary-i386/Packages.gz + af51d9566c47b93ca2bbd2004db83f90e3598e76b3925781358781799a24c39b 929 main/binary-i386/Packages.bz2 diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish4Test_gold b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish4Test_gold new file mode 100644 index 00000000..b7f3428f --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish4Test_gold @@ -0,0 +1,4 @@ +Published repositories: + * swift:test1:./maverick [amd64, i386] publishes {main: [local-repo]} + * swift:test1:./xyz [amd64, i386] publishes {main: [local-repo]} + * swift:test1:prefix/maverick [amd64, i386] publishes {main: [local-repo]} diff --git a/src/github.com/smira/aptly/system/t06_publish/SwiftPublish5Test_gold b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish5Test_gold new file mode 100644 index 00000000..cadce02c --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/SwiftPublish5Test_gold @@ -0,0 +1,3 @@ +Cleaning up prefix "." components main... + +Published repository has been removed successfully. diff --git a/src/github.com/smira/aptly/system/t06_publish/__init__.py b/src/github.com/smira/aptly/system/t06_publish/__init__.py index 45ba80df..3531ee1c 100644 --- a/src/github.com/smira/aptly/system/t06_publish/__init__.py +++ b/src/github.com/smira/aptly/system/t06_publish/__init__.py @@ -9,3 +9,4 @@ from .snapshot import * from .switch import * from .update import * from .s3 import * +from .swift import * diff --git a/src/github.com/smira/aptly/system/t06_publish/swift.py b/src/github.com/smira/aptly/system/t06_publish/swift.py new file mode 100644 index 00000000..cdccaa0b --- /dev/null +++ b/src/github.com/smira/aptly/system/t06_publish/swift.py @@ -0,0 +1,158 @@ +from swift_lib import SwiftTest + + +def strip_processor(output): + return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:')]) + + +class SwiftPublish1Test(SwiftTest): + """ + publish to Swift: from repo + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${files}", + ] + runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo swift:test1:" + + def check(self): + super(SwiftPublish1Test, self).check() + + self.check_exists('dists/maverick/InRelease') + self.check_exists('dists/maverick/Release') + self.check_exists('dists/maverick/Release.gpg') + + self.check_exists('dists/maverick/main/binary-i386/Packages') + self.check_exists('dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('dists/maverick/main/source/Sources') + self.check_exists('dists/maverick/main/source/Sources.gz') + self.check_exists('dists/maverick/main/source/Sources.bz2') + + self.check_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_exists('pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_exists('pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + # # verify contents except of sums + self.check_file_contents('dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('dists/maverick/main/source/Sources', 'sources', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + self.check_file_contents('dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class SwiftPublish2Test(SwiftTest): + """ + publish to Swift: publish update removed some packages + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${files}/", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo swift:test1:", + "aptly repo remove local-repo pyspi" + ] + runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick swift:test1:" + + def check(self): + super(SwiftPublish2Test, self).check() + + self.check_exists('dists/maverick/InRelease') + self.check_exists('dists/maverick/Release') + self.check_exists('dists/maverick/Release.gpg') + + self.check_exists('dists/maverick/main/binary-i386/Packages') + self.check_exists('dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('dists/maverick/main/source/Sources') + self.check_exists('dists/maverick/main/source/Sources.gz') + self.check_exists('dists/maverick/main/source/Sources.bz2') + + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') + + # verify contents except of sums + self.check_file_contents('dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('dists/maverick/main/source/Sources', 'sources', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + self.check_file_contents('dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class SwiftPublish3Test(SwiftTest): + """ + publish to Swift: publish switch - removed some packages + """ + fixtureDB = True + fixturePool = True + fixtureCmds = [ + "aptly snapshot create snap1 from mirror gnuplot-maverick", + "aptly snapshot create snap2 empty", + "aptly snapshot pull -no-deps -architectures=i386,amd64 snap2 snap1 snap3 gnuplot-x11", + "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick snap1 swift:test1:", + ] + runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick swift:test1: snap3" + + def check(self): + super(SwiftPublish3Test, self).check() + + self.check_exists('dists/maverick/InRelease') + self.check_exists('dists/maverick/Release') + self.check_exists('dists/maverick/Release.gpg') + + self.check_exists('dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('dists/maverick/main/binary-amd64/Packages') + self.check_exists('dists/maverick/main/binary-amd64/Packages.gz') + self.check_exists('dists/maverick/main/binary-amd64/Packages.bz2') + + self.check_exists('pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_i386.deb') + self.check_exists('pool/main/g/gnuplot/gnuplot-x11_4.6.1-1~maverick2_amd64.deb') + self.check_not_exists('pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_i386.deb') + self.check_not_exists('pool/main/g/gnuplot/gnuplot-nox_4.6.1-1~maverick2_amd64.deb') + + # verify contents except of sums + self.check_file_contents('dists/maverick/Release', 'release', match_prepare=strip_processor) + self.check_file_contents('dists/maverick/main/binary-i386/Packages', 'binary', match_prepare=lambda s: "\n".join(sorted(s.split("\n")))) + + +class SwiftPublish4Test(SwiftTest): + """ + publish to Swift: multiple repos, list + """ + fixtureCmds = [ + "aptly repo create -distribution=maverick local-repo", + "aptly repo add local-repo ${udebs}", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo swift:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=xyz local-repo swift:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo swift:test1:prefix", + ] + runCmd = "aptly publish list" + + +class SwiftPublish5Test(SwiftTest): + """ + publish to Swift: publish drop - component cleanup + """ + fixtureCmds = [ + "aptly repo create local1", + "aptly repo create local2", + "aptly repo add local1 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb", + "aptly repo add local2 ${files}", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq1 local1 swift:test1:", + "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq2 local2 swift:test1:", + ] + runCmd = "aptly publish drop sq2 swift:test1:" + + def check(self): + super(SwiftPublish5Test, self).check() + + self.check_exists('dists/sq1') + self.check_not_exists('dists/sq2') + self.check_exists('pool/main/') + + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists('pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb') diff --git a/src/github.com/smira/aptly/system/t06_publish/switch.py b/src/github.com/smira/aptly/system/t06_publish/switch.py index 0abd8dd5..51bfc82f 100644 --- a/src/github.com/smira/aptly/system/t06_publish/switch.py +++ b/src/github.com/smira/aptly/system/t06_publish/switch.py @@ -388,3 +388,15 @@ class PublishSwitch11Test(BaseTest): self.check_file_contents("public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz", "file") + +class PublishSwitch12Test(BaseTest): + """ + publish switch: wrong component names + """ + fixtureCmds = [ + "aptly snapshot create snap1 empty", + "aptly snapshot create snap2 empty", + "aptly publish snapshot -architectures=i386 -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=a,b snap1 snap2", + ] + runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=a,c maverick snap2 snap1" + expectedCode = 1 diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1-1.3.conflict.dsc b/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1-1.3.conflict.dsc new file mode 100644 index 00000000..21a67a86 --- /dev/null +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1-1.3.conflict.dsc @@ -0,0 +1,12 @@ +Format: 1.0 +Source: pyspi +Binary: python-at-spi +Architecture: any +Version: 0.6.1-1.3 +Maintainer: Jose Carlos Garcia Sogo +Homepage: http://people.redhat.com/zcerza/dogtail +Standards-Version: 3.7.3 +Vcs-Svn: svn://svn.tribulaciones.org/srv/svn/pyspi/trunk +Build-Depends: debhelper (>= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev +Files: + d41d8cd98f00b204e9800998ecf8427e 0 pyspi_0.6.1.orig.tar.gz diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1.orig.tar.gz b/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test/pyspi_0.6.1.orig.tar.gz new file mode 100644 index 00000000..e69de29b diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test_gold new file mode 100644 index 00000000..138adc29 --- /dev/null +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB10Test_gold @@ -0,0 +1,7 @@ +Loading mirrors, local repos, snapshots and published repos... +Loading list of all packages... +Deleting unreferenced packages (0)... +Building list of files referenced by packages... +Building list of files in package pool... +Deleting unreferenced files (0)... +Compacting database... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB1Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB1Test_gold index 1816ebdd..138adc29 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB1Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB1Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (0)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB2Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB2Test_gold index b17771cd..1f289e67 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB2Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB2Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (73270)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB3Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB3Test_gold index 5f9cd0eb..73279e14 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB3Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB3Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (7)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB4Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB4Test_gold index 1816ebdd..138adc29 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB4Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB4Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (0)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB5Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB5Test_gold index 5f9cd0eb..73279e14 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB5Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB5Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (7)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB6Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB6Test_gold index 1816ebdd..138adc29 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB6Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB6Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (0)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB7Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB7Test_gold index 1816ebdd..138adc29 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB7Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB7Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (0)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB8Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB8Test_gold index d5a2e771..fc57cfb9 100644 --- a/src/github.com/smira/aptly/system/t08_db/CleanupDB8Test_gold +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB8Test_gold @@ -1,4 +1,4 @@ -Loading mirrors, local repos and snapshots... +Loading mirrors, local repos, snapshots and published repos... Loading list of all packages... Deleting unreferenced packages (3)... Building list of files referenced by packages... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_gold b/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_gold new file mode 100644 index 00000000..138adc29 --- /dev/null +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_gold @@ -0,0 +1,7 @@ +Loading mirrors, local repos, snapshots and published repos... +Loading list of all packages... +Deleting unreferenced packages (0)... +Building list of files referenced by packages... +Building list of files in package pool... +Deleting unreferenced files (0)... +Compacting database... diff --git a/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_publish_drop b/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_publish_drop new file mode 100644 index 00000000..c9303809 --- /dev/null +++ b/src/github.com/smira/aptly/system/t08_db/CleanupDB9Test_publish_drop @@ -0,0 +1,4 @@ +Removing ${HOME}/.aptly/public/dists/def... +Cleaning up prefix "." components main... + +Published repository has been removed successfully. diff --git a/src/github.com/smira/aptly/system/t08_db/cleanup.py b/src/github.com/smira/aptly/system/t08_db/cleanup.py index 0003d16e..ae3f0bf0 100644 --- a/src/github.com/smira/aptly/system/t08_db/cleanup.py +++ b/src/github.com/smira/aptly/system/t08_db/cleanup.py @@ -92,3 +92,35 @@ class CleanupDB8Test(BaseTest): "aptly repo drop local-repo", ] runCmd = "aptly db cleanup" + + +class CleanupDB9Test(BaseTest): + """ + cleanup db: publish local repo, remove packages from repo, db cleanup + """ + fixtureCmds = [ + "aptly repo create -distribution=abc local-repo", + "aptly repo create -distribution=def local-repo2", + "aptly repo add local-repo ${files}", + "aptly publish repo -skip-signing local-repo", + "aptly publish repo -skip-signing -architectures=i386 local-repo2", + "aptly repo remove local-repo Name", + ] + runCmd = "aptly db cleanup" + + def check(self): + self.check_output() + self.check_cmd_output("aptly publish drop def", "publish_drop", match_prepare=self.expand_environ) + + +class CleanupDB10Test(BaseTest): + """ + cleanup db: conflict in packages, should not cleanup anything + """ + fixtureCmds = [ + "aptly repo create a", + "aptly repo create b", + "aptly repo add a ${files}", + "aptly repo add b ${testfiles}" + ] + runCmd = "aptly db cleanup" diff --git a/src/github.com/smira/aptly/system/t09_repo/AddRepo14Test_gold b/src/github.com/smira/aptly/system/t09_repo/AddRepo14Test_gold new file mode 100644 index 00000000..d08ab1e1 --- /dev/null +++ b/src/github.com/smira/aptly/system/t09_repo/AddRepo14Test_gold @@ -0,0 +1,2 @@ +Loading packages... +[+] libboost-program-options-dev_1.49.0.1_i386 added diff --git a/src/github.com/smira/aptly/system/t09_repo/add.py b/src/github.com/smira/aptly/system/t09_repo/add.py index 9b5d9319..3c26cea1 100644 --- a/src/github.com/smira/aptly/system/t09_repo/add.py +++ b/src/github.com/smira/aptly/system/t09_repo/add.py @@ -267,3 +267,19 @@ class AddRepo13Test(BaseTest): # check pool self.check_exists('pool/72/16/dmraid-udeb_1.0.0.rc16-4.1_amd64.udeb') self.check_exists('pool/b7/2c/pyspi_0.6.1-1.3.dsc') + + +class AddRepo14Test(BaseTest): + """ + add same package to local repo twice and make sure the file doesn't get truncated. + """ + fixtureCmds = [ + "aptly repo create -comment=Repo14 -distribution=squeeze repo14", + "aptly repo add repo14 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb" + ] + runCmd = "aptly repo add repo14 $aptlyroot/pool/00/35/libboost-program-options-dev_1.49.0.1_i386.deb" + + def check(self): + super(AddRepo14Test, self).check() + # check pool + self.check_file_not_empty('pool/00/35/libboost-program-options-dev_1.49.0.1_i386.deb') diff --git a/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold b/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold index 30358516..54d8db36 100644 --- a/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold +++ b/src/github.com/smira/aptly/system/t10_task/RunTask1Test_gold @@ -21,6 +21,6 @@ End command output: ------------------------------ 4) [Running]: version Begin command output: ---------------------------- -aptly version: 0.8~dev +aptly version: 0.9.1 End command output: ------------------------------ diff --git a/src/github.com/smira/aptly/system/t10_task/__init__.py b/src/github.com/smira/aptly/system/t10_task/__init__.py index dd3ca362..00d32dc8 100644 --- a/src/github.com/smira/aptly/system/t10_task/__init__.py +++ b/src/github.com/smira/aptly/system/t10_task/__init__.py @@ -2,5 +2,4 @@ Test aptly task run """ -# disabled for now -#from .run import * +from .run import * diff --git a/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold b/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold index e69de29b..2d08b563 100644 --- a/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold +++ b/src/github.com/smira/aptly/system/t11_package/SearchPackage2Test_gold @@ -0,0 +1 @@ +ERROR: no results diff --git a/src/github.com/smira/aptly/system/t11_package/search.py b/src/github.com/smira/aptly/system/t11_package/search.py index fee0d545..a8feac90 100644 --- a/src/github.com/smira/aptly/system/t11_package/search.py +++ b/src/github.com/smira/aptly/system/t11_package/search.py @@ -15,6 +15,7 @@ class SearchPackage2Test(BaseTest): search package: missing package """ runCmd = "aptly package search 'Name (package-xx)'" + expectedCode = 1 class SearchPackage3Test(BaseTest): diff --git a/src/github.com/smira/aptly/system/t12_api/__init__.py b/src/github.com/smira/aptly/system/t12_api/__init__.py new file mode 100644 index 00000000..e2e8a5df --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/__init__.py @@ -0,0 +1,11 @@ +""" +Testing aptly REST API +""" + +from .repos import * +from .files import * +from .publish import * +from .version import * +from .graph import * +from .snapshots import * +from .packages import * diff --git a/src/github.com/smira/aptly/system/t12_api/files.py b/src/github.com/smira/aptly/system/t12_api/files.py new file mode 100644 index 00000000..545eecc1 --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/files.py @@ -0,0 +1,98 @@ +from api_lib import APITest + + +class FilesAPITestUpload(APITest): + """ + POST /files/:dir + """ + + def check(self): + d = self.random_name() + resp = self.upload("/api/files/" + d, "pyspi_0.6.1-1.3.dsc") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), [d + '/pyspi_0.6.1-1.3.dsc']) + self.check_exists("upload/" + d + '/pyspi_0.6.1-1.3.dsc') + + +class FilesAPITestUploadMulti(APITest): + """ + POST /files/:dir, GET /files/:dir multi files + """ + + def check(self): + d = self.random_name() + + self.check_equal(self.get("/api/files/" + d).status_code, 404) + + resp = self.upload("/api/files/" + d, "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz") + self.check_equal(resp.status_code, 200) + self.check_equal(sorted(resp.json()), + [d + '/pyspi_0.6.1-1.3.diff.gz', d + '/pyspi_0.6.1-1.3.dsc', d + '/pyspi_0.6.1.orig.tar.gz']) + self.check_exists("upload/" + d + '/pyspi_0.6.1-1.3.dsc') + self.check_exists("upload/" + d + '/pyspi_0.6.1-1.3.diff.gz') + self.check_exists("upload/" + d + '/pyspi_0.6.1.orig.tar.gz') + + resp = self.get("/api/files/" + d) + self.check_equal(resp.status_code, 200) + self.check_equal(sorted(resp.json()), + ['pyspi_0.6.1-1.3.diff.gz', 'pyspi_0.6.1-1.3.dsc', 'pyspi_0.6.1.orig.tar.gz']) + + +class FilesAPITestList(APITest): + """ + GET /files/ + """ + + def check(self): + d1, d2, d3 = self.random_name(), self.random_name(), self.random_name() + + resp = self.get("/api/files") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) + + self.check_equal(self.upload("/api/files/" + d1, "pyspi_0.6.1-1.3.dsc").status_code, 200) + self.check_equal(self.upload("/api/files/" + d2, "pyspi_0.6.1-1.3.dsc").status_code, 200) + self.check_equal(self.upload("/api/files/" + d3, "pyspi_0.6.1-1.3.dsc").status_code, 200) + + resp = self.get("/api/files") + self.check_equal(resp.status_code, 200) + self.check_equal(sorted(resp.json()), sorted([d1, d2, d3])) + + +class FilesAPITestDelete(APITest): + """ + DELETE /files/:dir, DELETE /files/:dir/:name + """ + + def check(self): + d1, d2 = self.random_name(), self.random_name() + + self.check_equal(self.get("/api/files").json(), []) + self.check_equal(self.delete("/api/files/" + d1).status_code, 200) + self.check_equal(self.delete("/api/files/" + d1 + "/" + "pyspi_0.6.1-1.3.dsc").status_code, 200) + + self.check_equal(self.upload("/api/files/" + d1, "pyspi_0.6.1-1.3.dsc").status_code, 200) + self.check_equal(self.upload("/api/files/" + d2, "pyspi_0.6.1-1.3.dsc").status_code, 200) + + self.check_equal(self.delete("/api/files/" + d1).status_code, 200) + self.check_equal(self.get("/api/files").json(), [d2]) + + self.check_equal(self.delete("/api/files/" + d2 + "/" + "no-such-file").status_code, 200) + self.check_equal(self.get("/api/files/" + d2).json(), ["pyspi_0.6.1-1.3.dsc"]) + + self.check_equal(self.delete("/api/files/" + d2 + "/" + "pyspi_0.6.1-1.3.dsc").status_code, 200) + self.check_equal(self.get("/api/files").json(), [d2]) + self.check_equal(self.get("/api/files/" + d2).json(), []) + + +class FilesAPITestSecurity(APITest): + """ + delete & upload security + """ + + def check(self): + self.check_equal(self.delete("/api/files/.").status_code, 400) + self.check_equal(self.delete("/api/files").status_code, 404) + self.check_equal(self.delete("/api/files/../.").status_code, 400) + self.check_equal(self.delete("/api/files/./..").status_code, 400) + self.check_equal(self.delete("/api/files/dir/..").status_code, 400) diff --git a/src/github.com/smira/aptly/system/t12_api/graph.py b/src/github.com/smira/aptly/system/t12_api/graph.py new file mode 100644 index 00000000..2f2a9f10 --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/graph.py @@ -0,0 +1,17 @@ +from api_lib import APITest + + +class GraphAPITest(APITest): + """ + GET /graph.:ext + """ + + def check(self): + resp = self.get("/api/graph.png") + self.check_equal(resp.headers["Content-Type"], "image/png") + self.check_equal(resp.content[:4], '\x89PNG') + + self.check_equal(self.post("/api/repos", json={"Name": "xyz", "Comment": "fun repo"}).status_code, 201) + resp = self.get("/api/graph.svg") + self.check_equal(resp.headers["Content-Type"], "image/svg+xml") + self.check_equal(resp.content[:4], '= 5), cdbs, libatspi-dev, python-pyrex, python-support (>= 0.4), python-all-dev, libx11-dev', + 'Checksums-Sha1': ' 95a2468e4bbce730ba286f2211fa41861b9f1d90 3456 pyspi_0.6.1-1.3.diff.gz\n 56c8a9b1f4ab636052be8966690998cbe865cd6c 1782 pyspi_0.6.1-1.3.dsc\n 9694b80acc171c0a5bc99f707933864edfce555e 29063 pyspi_0.6.1.orig.tar.gz\n', + 'Checksums-Sha256': ' 2e770b28df948f3197ed0b679bdea99f3f2bf745e9ddb440c677df9c3aeaee3c 3456 pyspi_0.6.1-1.3.diff.gz\n d494aaf526f1ec6b02f14c2f81e060a5722d6532ddc760ec16972e45c2625989 1782 pyspi_0.6.1-1.3.dsc\n 64069ee828c50b1c597d10a3fefbba279f093a4723965388cdd0ac02f029bfb9 29063 pyspi_0.6.1.orig.tar.gz\n', + 'Files': ' 22ff26db69b73d3438fdde21ab5ba2f1 3456 pyspi_0.6.1-1.3.diff.gz\n b72cb94699298a117b7c82641c68b6fd 1782 pyspi_0.6.1-1.3.dsc\n def336bd566ea688a06ec03db7ccf1f4 29063 pyspi_0.6.1.orig.tar.gz\n', + 'FilesHash': '3a8b37cbd9a3559e', + 'Format': '1.0', + 'Homepage': 'http://people.redhat.com/zcerza/dogtail', + 'Key': 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', + 'Maintainer': 'Jose Carlos Garcia Sogo ', + 'Package': 'pyspi', + 'ShortKey': 'Psource pyspi 0.6.1-1.3', + 'Standards-Version': '3.7.3', + 'Vcs-Svn': 'svn://svn.tribulaciones.org/srv/svn/pyspi/trunk', + 'Version': '0.6.1-1.3'}) + + resp = self.get("/api/packages/" + urllib.quote('Pamd64 no-such-package 1.0 3a8b37cbd9a3559e')) + self.check_equal(resp.status_code, 404) diff --git a/src/github.com/smira/aptly/system/t12_api/publish.py b/src/github.com/smira/aptly/system/t12_api/publish.py new file mode 100644 index 00000000..8ff020df --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/publish.py @@ -0,0 +1,278 @@ +import os +import inspect + +from api_lib import APITest + +DefaultSigningOptions = { + "Keyring": os.path.join(os.path.dirname(inspect.getsourcefile(APITest)), "files") + "/aptly.pub", + "SecretKeyring": os.path.join(os.path.dirname(inspect.getsourcefile(APITest)), "files") + "/aptly.sec", +} + + +class PublishAPITestRepo(APITest): + """ + POST /publish/:prefix (local repos), GET /publish + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + # publishing under prefix, default distribution + prefix = self.random_name() + resp = self.post("/api/publish/" + prefix, + json={ + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + }) + repo_expected = { + 'Architectures': ['i386', 'source'], + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'Prefix': prefix, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': ''} + + self.check_equal(resp.status_code, 201) + self.check_equal(resp.json(), repo_expected) + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + + self.check_exists("public/" + prefix + "/dists/wheezy/Release") + self.check_exists("public/" + prefix + "/dists/wheezy/main/binary-i386/Packages") + self.check_exists("public/" + prefix + "/dists/wheezy/main/source/Sources") + self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + + # publishing under root, custom distribution, architectures + distribution = self.random_name() + resp = self.post("/api/publish", + json={ + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + "Distribution": distribution, + "Architectures": ["i386", "amd64"], + }) + repo2_expected = { + 'Architectures': ['amd64', 'i386'], + 'Distribution': distribution, + 'Label': '', + 'Origin': '', + 'Prefix': ".", + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': ''} + self.check_equal(resp.status_code, 201) + self.check_equal(resp.json(), repo2_expected) + + self.check_exists("public/dists/" + distribution + "/Release") + self.check_exists("public/dists/" + distribution + "/main/binary-i386/Packages") + self.check_exists("public/dists/" + distribution + "/main/binary-amd64/Packages") + self.check_exists("public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + + all_repos = self.get("/api/publish") + self.check_equal(all_repos.status_code, 200) + self.check_in(repo_expected, all_repos.json()) + self.check_in(repo2_expected, all_repos.json()) + + +class PublishSnapshotAPITest(APITest): + """ + POST /publish/:prefix (snapshots), GET /publish + """ + + def check(self): + repo_name = self.random_name() + snapshot_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) + + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_equal(resp.status_code, 400) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}).status_code, 201) + + prefix = self.random_name() + resp = self.post("/api/publish/" + prefix, + json={ + "SourceKind": "snapshot", + "Sources": [{"Name": snapshot_name}], + "Signing": DefaultSigningOptions, + "Distribution": "squeeze", + }) + self.check_equal(resp.status_code, 201) + self.check_equal(resp.json(), { + 'Architectures': ['i386'], + 'Distribution': 'squeeze', + 'Label': '', + 'Origin': '', + 'Prefix': prefix, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot_name}], + 'Storage': ''}) + + self.check_exists("public/" + prefix + "/dists/squeeze/Release") + self.check_exists("public/" + prefix + "/dists/squeeze/main/binary-i386/Packages") + self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + + +class PublishUpdateAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution (local repos), DELETE /publish/:prefix/:distribution + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + prefix = self.random_name() + resp = self.post("/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + }) + + self.check_equal(resp.status_code, 201) + + self.check_not_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + self.check_equal(self.delete("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).status_code, 200) + + resp = self.put("/api/publish/" + prefix + "/wheezy", + json={ + "Signing": DefaultSigningOptions, + }) + repo_expected = { + 'Architectures': ['i386', 'source'], + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'Prefix': prefix, + 'SourceKind': 'local', + 'Sources': [{'Component': 'main', 'Name': repo_name}], + 'Storage': ''} + + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), repo_expected) + + self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_not_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy").status_code, 200) + self.check_not_exists("public/" + prefix + "dists/") + + +class PublishSwitchAPITestRepo(APITest): + """ + PUT /publish/:prefix/:distribution (snapshots), DELETE /publish/:prefix/:distribution + """ + fixtureGpg = True + + def check(self): + repo_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + snapshot1_name = self.random_name() + self.check_equal(self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot1_name}).status_code, 201) + + prefix = self.random_name() + resp = self.post("/api/publish/" + prefix, + json={ + "Architectures": ["i386", "source"], + "SourceKind": "snapshot", + "Sources": [{"Name": snapshot1_name}], + "Signing": DefaultSigningOptions, + }) + + self.check_equal(resp.status_code, 201) + repo_expected = { + 'Architectures': ['i386', 'source'], + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'Prefix': prefix, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot1_name}], + 'Storage': ''} + self.check_equal(resp.json(), repo_expected) + + self.check_not_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + self.check_equal(self.delete("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).status_code, 200) + + snapshot2_name = self.random_name() + self.check_equal(self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot2_name}).status_code, 201) + + resp = self.put("/api/publish/" + prefix + "/wheezy", + json={ + "Snapshots": [{"Component": "main", "Name": snapshot2_name}], + "Signing": DefaultSigningOptions, + }) + repo_expected = { + 'Architectures': ['i386', 'source'], + 'Distribution': 'wheezy', + 'Label': '', + 'Origin': '', + 'Prefix': prefix, + 'SourceKind': 'snapshot', + 'Sources': [{'Component': 'main', 'Name': snapshot2_name}], + 'Storage': ''} + + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), repo_expected) + + self.check_exists("public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_not_exists("public/" + prefix + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + + self.check_equal(self.delete("/api/publish/" + prefix + "/wheezy").status_code, 200) + self.check_not_exists("public/" + prefix + "dists/") diff --git a/src/github.com/smira/aptly/system/t12_api/repos.py b/src/github.com/smira/aptly/system/t12_api/repos.py new file mode 100644 index 00000000..0e7feb60 --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/repos.py @@ -0,0 +1,296 @@ +from api_lib import APITest +from publish import DefaultSigningOptions + + +class ReposAPITestCreateShow(APITest): + """ + GET /api/repos/:name, POST /api/repos, GET /api/repos/:name/packages + """ + def check(self): + repo_name = self.random_name() + repo_desc = {u'Comment': u'fun repo', + u'DefaultComponent': u'', + u'DefaultDistribution': u'', + u'Name': repo_name} + + resp = self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}) + self.check_equal(resp.json(), repo_desc) + self.check_equal(resp.status_code, 201) + + self.check_equal(self.get("/api/repos/" + repo_name).json(), repo_desc) + self.check_equal(self.get("/api/repos/" + repo_name).status_code, 200) + + resp = self.get("/api/repos/" + repo_name + "/packages") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) + + self.check_equal(self.get("/api/repos/" + self.random_name()).status_code, 404) + + +class ReposAPITestCreateIndexDelete(APITest): + """ + GET /api/repos, POST /api/repos, DELETE /api/repos/:name + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + repos = self.get("/api/repos/").json() + names = [repo["Name"] for repo in repos] + assert repo_name in names + + self.check_equal(self.delete("/api/repos/" + repo_name).status_code, 200) + self.check_equal(self.delete("/api/repos/" + repo_name).status_code, 404) + + self.check_equal(self.get("/api/repos/" + repo_name).status_code, 404) + + self.check_equal(self.delete("/api/repos/" + self.random_name()).status_code, 404) + + # create once again + distribution = self.random_name() + self.check_equal(self.post("/api/repos", + json={ + "Name": repo_name, + "Comment": "fun repo", + "DefaultDistribution": distribution + }).status_code, 201) + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) + + resp = self.post("/api/repos/" + repo_name + "/file/" + d) + self.check_equal(resp.status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/snapshots", json={"Name": repo_name}).status_code, 201) + + self.check_equal(self.post("/api/publish", + json={ + "SourceKind": "local", + "Sources": [{"Name": repo_name}], + "Signing": DefaultSigningOptions, + }).status_code, 201) + + # repo is not deletable while it is published + self.check_equal(self.delete("/api/repos/" + repo_name).status_code, 409) + self.check_equal(self.delete("/api/repos/" + repo_name, params={"force": "1"}).status_code, 409) + + # drop published + self.check_equal(self.delete("/api/publish//" + distribution).status_code, 200) + self.check_equal(self.delete("/api/repos/" + repo_name).status_code, 409) + self.check_equal(self.delete("/api/repos/" + repo_name, params={"force": "1"}).status_code, 200) + self.check_equal(self.get("/api/repos/" + repo_name).status_code, 404) + + +class ReposAPITestAdd(APITest): + """ + POST /api/repos/:name/file/:dir, GET /api/repos/:name/packages + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) + + resp = self.post("/api/repos/" + repo_name + "/file/" + d) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), { + u'FailedFiles': [], + u'Report': { + u'Added': [u'pyspi_0.6.1-1.3_source added'], + u'Removed': [], + u'Warnings': []}}) + + self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) + + self.check_not_exists("upload/" + d) + + +class ReposAPITestAddNotFullRemove(APITest): + """ + POST /api/repos/:name/file/:dir not all files removed + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", "aptly.pub").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) + + self.check_exists("upload/" + d + "/aptly.pub") + self.check_not_exists("upload/" + d + "/pyspi_0.6.1-1.3.dsc") + + +class ReposAPITestAddNoRemove(APITest): + """ + POST /api/repos/:name/file/:dir no remove + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "pyspi_0.6.1-1.3.dsc", "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d, params={"noRemove": 1}).status_code, 200) + self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) + + self.check_exists("upload/" + d + "/pyspi_0.6.1-1.3.dsc") + + +class ReposAPITestAddFile(APITest): + """ + POST /api/repos/:name/file/:dir/:file + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + resp = self.post("/api/repos/" + repo_name + "/file/" + d + "/libboost-program-options-dev_1.49.0.1_i386.deb") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), { + u'FailedFiles': [], + u'Report': { + u'Added': [u'libboost-program-options-dev_1.49.0.1_i386 added'], + u'Removed': [], + u'Warnings': []}}) + + self.check_equal(self.get("/api/repos/" + repo_name + "/packages").json(), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']) + + self.check_not_exists("upload/" + d) + + +class ReposAPITestShowQuery(APITest): + """ + GET /api/repos/:name/packages?q=query + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages", params={"q": "pyspi"}).json()), + ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages", params={"q": "Version (> 0.6.1-1.4)"}).json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + self.check_equal(sorted(p['Key'] for p in self.get("/api/repos/" + repo_name + "/packages", + params={"q": "pyspi", "format": "details"}).json()), + ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + resp = self.get("/api/repos/" + repo_name + "/packages", params={"q": "pyspi)"}) + self.check_equal(resp.status_code, 400) + self.check_equal(resp.json()[0]["error"], u'parsing failed: unexpected token ): expecting end of query') + + +class ReposAPITestAddMultiple(APITest): + """ + POST /api/repos/:name/file/:dir/:file multiple + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d + "/pyspi_0.6.1-1.3.dsc", + params={"noRemove": 1}).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d + "/pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + +class ReposAPITestPackagesAddDelete(APITest): + """ + POST/DELETE /api/repos/:name/packages + """ + def check(self): + repo_name = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name, "Comment": "fun repo"}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc", + "pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz", + "pyspi-0.6.1-1.3.stripped.dsc").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', + 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + self.check_equal(self.post("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', + 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + self.check_equal(self.post("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', + 'Psource no-such-package 0.6.1-1.4 f8f1daa806004e89']}).status_code, 404) + + self.check_equal(self.delete("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e']) + + self.check_equal(self.post("/api/repos/" + repo_name + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']}).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name + "/packages").json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e', + 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) + + repo_name2 = self.random_name() + + self.check_equal(self.post("/api/repos", json={"Name": repo_name2, "Comment": "fun repo"}).status_code, 201) + + self.check_equal(self.post("/api/repos/" + repo_name2 + "/packages/", + json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89', + 'Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378']}).status_code, 200) + + self.check_equal(sorted(self.get("/api/repos/" + repo_name2 + "/packages").json()), + ['Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Psource pyspi 0.6.1-1.4 f8f1daa806004e89']) diff --git a/src/github.com/smira/aptly/system/t12_api/snapshots.py b/src/github.com/smira/aptly/system/t12_api/snapshots.py new file mode 100644 index 00000000..994ef604 --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/snapshots.py @@ -0,0 +1,276 @@ +from api_lib import APITest +from publish import DefaultSigningOptions + + +class SnapshotsAPITestCreateShowEmpty(APITest): + """ + GET /api/snapshots/:name, POST /api/snapshots, GET /api/snapshots/:name/packages + """ + def check(self): + snapshot_name = self.random_name() + snapshot_desc = {u'Description': u'fun snapshot', + u'Name': snapshot_name} + + # create empty snapshot + resp = self.post("/api/snapshots", json=snapshot_desc) + self.check_subset(snapshot_desc, resp.json()) + self.check_equal(resp.status_code, 201) + + self.check_subset(snapshot_desc, self.get("/api/snapshots/" + snapshot_name).json()) + self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) + + resp = self.get("/api/snapshots/" + snapshot_name + "/packages") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) + + self.check_equal(self.get("/api/snapshots/" + self.random_name()).status_code, 404) + + # create snapshot with duplicate name + resp = self.post("/api/snapshots", json=snapshot_desc) + self.check_equal(resp.status_code, 400) + + +class SnapshotsAPITestCreateFromRefs(APITest): + """ + GET /api/snapshots/:name, POST /api/snapshots, GET /api/snapshots/:name/packages, + GET /api/snapshots + """ + def check(self): + snapshot_name = self.random_name() + snapshot_desc = {u'Description': u'fun snapshot', + u'Name': snapshot_name, + u'SourceSnapshots': [self.random_name()]} + + # creating snapshot from missing source snapshot + resp = self.post("/api/snapshots", json=snapshot_desc) + self.check_equal(resp.status_code, 404) + + # create empty snapshot + empty_snapshot_name = self.random_name() + resp = self.post("/api/snapshots", json={"Name": empty_snapshot_name}) + self.check_equal(resp.status_code, 201) + self.check_equal(resp.json()['Description'], 'Created as empty') + + # create and upload package to repo to register package in DB + repo_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + # create snapshot with empty snapshot as source and package + snapshot = snapshot_desc.copy() + snapshot['PackageRefs'] = ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"] + snapshot['SourceSnapshots'] = [empty_snapshot_name] + resp = self.post("/api/snapshots", json=snapshot) + self.check_equal(resp.status_code, 201) + snapshot.pop('SourceSnapshots') + snapshot.pop('PackageRefs') + self.check_subset(snapshot, resp.json()) + + self.check_subset(snapshot, self.get("/api/snapshots/" + snapshot_name).json()) + resp = self.get("/api/snapshots/" + snapshot_name + "/packages") + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"]) + + # create snapshot with unreferenced package + resp = self.post("/api/snapshots", json={ + "Name": self.random_name(), + "PackageRefs": ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378", "Pamd64 no-such-package 1.2 91"]}) + self.check_equal(resp.status_code, 404) + + # list snapshots + resp = self.get("/api/snapshots", params={"sort": "time"}) + self.check_equal(resp.status_code, 200) + self.check_equal([s["Name"] for s in resp.json() if s["Name"] in [empty_snapshot_name, snapshot_name]], + [empty_snapshot_name, snapshot_name]) + + +class SnapshotsAPITestCreateFromRepo(APITest): + """ + POST /api/repos, POST /api/repos/:name/snapshots, GET /api/snapshots/:name + """ + def check(self): + repo_name = self.random_name() + snapshot_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) + + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_equal(resp.status_code, 400) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_equal(resp.status_code, 201) + self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 200) + + self.check_subset({u'Architecture': 'i386', + u'Package': 'libboost-program-options-dev', + u'Version': '1.49.0.1', + 'FilesHash': '918d2f433384e378'}, + self.get("/api/snapshots/" + snapshot_name + "/packages", params={"format": "details"}).json()[0]) + + self.check_subset({u'Architecture': 'i386', + u'Package': 'libboost-program-options-dev', + u'Version': '1.49.0.1', + 'FilesHash': '918d2f433384e378'}, + self.get("/api/snapshots/" + snapshot_name + "/packages", + params={"format": "details", "q": "Version (> 0.6.1-1.4)"}).json()[0]) + + # duplicate snapshot name + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_equal(resp.status_code, 400) + + +class SnapshotsAPITestCreateUpdate(APITest): + """ + POST /api/snapshots, PUT /api/snapshots/:name, GET /api/snapshots/:name + """ + def check(self): + snapshot_name = self.random_name() + snapshot_desc = {u'Description': u'fun snapshot', + u'Name': snapshot_name} + + resp = self.post("/api/snapshots", json=snapshot_desc) + self.check_equal(resp.status_code, 201) + + new_snapshot_name = self.random_name() + resp = self.put("/api/snapshots/" + snapshot_name, json={'Name': new_snapshot_name, + 'Description': 'New description'}) + self.check_equal(resp.status_code, 200) + + resp = self.get("/api/snapshots/" + new_snapshot_name) + self.check_equal(resp.status_code, 200) + self.check_subset({"Name": new_snapshot_name, + "Description": "New description"}, resp.json()) + + # duplicate name + resp = self.put("/api/snapshots/" + new_snapshot_name, json={'Name': new_snapshot_name, + 'Description': 'New description'}) + self.check_equal(resp.status_code, 409) + + # missing snapshot + resp = self.put("/api/snapshots/" + snapshot_name, json={}) + self.check_equal(resp.status_code, 404) + + +class SnapshotsAPITestCreateDelete(APITest): + """ + POST /api/snapshots, DELETE /api/snapshots/:name, GET /api/snapshots/:name + """ + def check(self): + snapshot_name = self.random_name() + snapshot_desc = {u'Description': u'fun snapshot', + u'Name': snapshot_name} + + # deleting unreferenced snapshot + resp = self.post("/api/snapshots", json=snapshot_desc) + self.check_equal(resp.status_code, 201) + + self.check_equal(self.delete("/api/snapshots/" + snapshot_name).status_code, 200) + + self.check_equal(self.get("/api/snapshots/" + snapshot_name).status_code, 404) + + # deleting referenced snapshot + snap1, snap2 = self.random_name(), self.random_name() + self.check_equal(self.post("/api/snapshots", json={"Name": snap1}).status_code, 201) + self.check_equal(self.post("/api/snapshots", json={"Name": snap2, "SourceSnapshots": [snap1]}).status_code, 201) + + self.check_equal(self.delete("/api/snapshots/" + snap1).status_code, 409) + self.check_equal(self.get("/api/snapshots/" + snap1).status_code, 200) + self.check_equal(self.delete("/api/snapshots/" + snap1, params={"force": "1"}).status_code, 200) + self.check_equal(self.get("/api/snapshots/" + snap1).status_code, 404) + + # deleting published snapshot + resp = self.post("/api/publish", + json={ + "SourceKind": "snapshot", + "Distribution": "trusty", + "Architectures": ["i386"], + "Sources": [{"Name": snap2}], + "Signing": DefaultSigningOptions, + }) + self.check_equal(resp.status_code, 201) + + self.check_equal(self.delete("/api/snapshots/" + snap2).status_code, 409) + self.check_equal(self.delete("/api/snapshots/" + snap2, params={"force": "1"}).status_code, 409) + + +class SnapshotsAPITestSearch(APITest): + """ + POST /api/snapshots, GET /api/snapshots?sort=name, GET /api/snapshots/:name + """ + def check(self): + + repo_name = self.random_name() + self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) + + d = self.random_name() + snapshot_name = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshot_name}) + self.check_equal(resp.status_code, 201) + + resp = self.get("/api/snapshots/" + snapshot_name + "/packages", + params={"q": "libboost-program-options-dev", "format": "details"}) + self.check_equal(resp.status_code, 200) + + self.check_equal(len(resp.json()), 1) + self.check_equal(resp.json()[0]["Package"], "libboost-program-options-dev") + + resp = self.get("/api/snapshots/" + snapshot_name + "/packages") + self.check_equal(resp.status_code, 200) + + self.check_equal(len(resp.json()), 1) + self.check_equal(resp.json(), ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"]) + + +class SnapshotsAPITestDiff(APITest): + """ + GET /api/snapshot/:name/diff/:name2 + """ + def check(self): + repos = [self.random_name() for x in xrange(2)] + snapshots = [self.random_name() for x in xrange(2)] + + for repo_name in repos: + self.check_equal(self.post("/api/repos", json={"Name": repo_name}).status_code, 201) + + d = self.random_name() + self.check_equal(self.upload("/api/files/" + d, + "libboost-program-options-dev_1.49.0.1_i386.deb").status_code, 200) + + self.check_equal(self.post("/api/repos/" + repo_name + "/file/" + d).status_code, 200) + + resp = self.post("/api/repos/" + repo_name + '/snapshots', json={'Name': snapshots[0]}) + self.check_equal(resp.status_code, 201) + + resp = self.post("/api/snapshots", json={'Name': snapshots[1]}) + self.check_equal(resp.status_code, 201) + + resp = self.get("/api/snapshots/" + snapshots[0] + "/diff/" + snapshots[1]) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), [{'Left': 'Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Right': None}]) + + resp = self.get("/api/snapshots/" + snapshots[1] + "/diff/" + snapshots[0]) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), [{'Right': 'Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378', + 'Left': None}]) + + resp = self.get("/api/snapshots/" + snapshots[0] + "/diff/" + snapshots[0]) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) + + resp = self.get("/api/snapshots/" + snapshots[1] + "/diff/" + snapshots[1]) + self.check_equal(resp.status_code, 200) + self.check_equal(resp.json(), []) diff --git a/src/github.com/smira/aptly/system/t12_api/version.py b/src/github.com/smira/aptly/system/t12_api/version.py new file mode 100644 index 00000000..e8885230 --- /dev/null +++ b/src/github.com/smira/aptly/system/t12_api/version.py @@ -0,0 +1,10 @@ +from api_lib import APITest + + +class VersionAPITest(APITest): + """ + GET /version + """ + + def check(self): + self.check_equal(self.get("/api/version").json(), {'Version': '0.9.1'}) diff --git a/src/github.com/smira/aptly/utils/checksum_test.go b/src/github.com/smira/aptly/utils/checksum_test.go index 5b1cfd5c..b2ff1eca 100644 --- a/src/github.com/smira/aptly/utils/checksum_test.go +++ b/src/github.com/smira/aptly/utils/checksum_test.go @@ -2,8 +2,9 @@ package utils import ( "io/ioutil" - . "launchpad.net/gocheck" "os" + + . "gopkg.in/check.v1" ) type ChecksumSuite struct { diff --git a/src/github.com/smira/aptly/utils/compress_test.go b/src/github.com/smira/aptly/utils/compress_test.go index 3763a167..6539ce6e 100644 --- a/src/github.com/smira/aptly/utils/compress_test.go +++ b/src/github.com/smira/aptly/utils/compress_test.go @@ -4,8 +4,9 @@ import ( "compress/bzip2" "compress/gzip" "io/ioutil" - . "launchpad.net/gocheck" "os" + + . "gopkg.in/check.v1" ) type CompressSuite struct { diff --git a/src/github.com/smira/aptly/utils/config.go b/src/github.com/smira/aptly/utils/config.go index 96755a76..1eb936dc 100644 --- a/src/github.com/smira/aptly/utils/config.go +++ b/src/github.com/smira/aptly/utils/config.go @@ -8,20 +8,21 @@ import ( // ConfigStructure is structure of main configuration type ConfigStructure struct { - RootDir string `json:"rootDir"` - DownloadConcurrency int `json:"downloadConcurrency"` - DownloadLimit int64 `json:"downloadSpeedLimit"` - Architectures []string `json:"architectures"` - DepFollowSuggests bool `json:"dependencyFollowSuggests"` - DepFollowRecommends bool `json:"dependencyFollowRecommends"` - DepFollowAllVariants bool `json:"dependencyFollowAllVariants"` - DepFollowSource bool `json:"dependencyFollowSource"` - GpgDisableSign bool `json:"gpgDisableSign"` - GpgDisableVerify bool `json:"gpgDisableVerify"` - DownloadSourcePackages bool `json:"downloadSourcePackages"` - PpaDistributorID string `json:"ppaDistributorID"` - PpaCodename string `json:"ppaCodename"` - S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints"` + RootDir string `json:"rootDir"` + DownloadConcurrency int `json:"downloadConcurrency"` + DownloadLimit int64 `json:"downloadSpeedLimit"` + Architectures []string `json:"architectures"` + DepFollowSuggests bool `json:"dependencyFollowSuggests"` + DepFollowRecommends bool `json:"dependencyFollowRecommends"` + DepFollowAllVariants bool `json:"dependencyFollowAllVariants"` + DepFollowSource bool `json:"dependencyFollowSource"` + GpgDisableSign bool `json:"gpgDisableSign"` + GpgDisableVerify bool `json:"gpgDisableVerify"` + DownloadSourcePackages bool `json:"downloadSourcePackages"` + PpaDistributorID string `json:"ppaDistributorID"` + PpaCodename string `json:"ppaCodename"` + S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints"` + SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints"` } // S3PublishRoot describes single S3 publishing entry point @@ -37,6 +38,17 @@ type S3PublishRoot struct { PlusWorkaround bool `json:"plusWorkaround"` } +// SwiftPublishRoot describes single OpenStack Swift publishing entry point +type SwiftPublishRoot struct { + UserName string `json:"osname"` + Password string `json:"password"` + AuthURL string `json:"authurl"` + Tenant string `json:"tenant"` + TenantID string `json:"tenantid"` + Prefix string `json:"prefix"` + Container string `json:"container"` +} + // Config is configuration for aptly, shared by all modules var Config = ConfigStructure{ RootDir: filepath.Join(os.Getenv("HOME"), ".aptly"), @@ -53,6 +65,7 @@ var Config = ConfigStructure{ PpaDistributorID: "ubuntu", PpaCodename: "", S3PublishRoots: map[string]S3PublishRoot{}, + SwiftPublishRoots: map[string]SwiftPublishRoot{}, } // LoadConfig loads configuration from json file diff --git a/src/github.com/smira/aptly/utils/config_test.go b/src/github.com/smira/aptly/utils/config_test.go index cc56cdb5..a90650df 100644 --- a/src/github.com/smira/aptly/utils/config_test.go +++ b/src/github.com/smira/aptly/utils/config_test.go @@ -1,9 +1,10 @@ package utils import ( - . "launchpad.net/gocheck" "os" "path/filepath" + + . "gopkg.in/check.v1" ) type ConfigSuite struct { @@ -29,10 +30,13 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { s.config.RootDir = "/tmp/aptly" s.config.DownloadConcurrency = 5 - s.config.S3PublishRoots = map[string]S3PublishRoot{"test": S3PublishRoot{ + s.config.S3PublishRoots = map[string]S3PublishRoot{"test": { Region: "us-east-1", Bucket: "repo"}} + s.config.SwiftPublishRoots = map[string]SwiftPublishRoot{"test": { + Container: "repo"}} + err := SaveConfig(configname, &s.config) c.Assert(err, IsNil) @@ -70,6 +74,17 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { " \"encryptionMethod\": \"\",\n"+ " \"plusWorkaround\": false\n"+ " }\n"+ + " },\n"+ + " \"SwiftPublishEndpoints\": {\n"+ + " \"test\": {\n"+ + " \"osname\": \"\",\n"+ + " \"password\": \"\",\n"+ + " \"authurl\": \"\",\n"+ + " \"tenant\": \"\",\n"+ + " \"tenantid\": \"\",\n"+ + " \"prefix\": \"\",\n"+ + " \"container\": \"repo\"\n"+ + " }\n"+ " }\n"+ "}") } diff --git a/src/github.com/smira/aptly/utils/gpg.go b/src/github.com/smira/aptly/utils/gpg.go index 90244192..080aa74a 100644 --- a/src/github.com/smira/aptly/utils/gpg.go +++ b/src/github.com/smira/aptly/utils/gpg.go @@ -18,6 +18,7 @@ type Signer interface { SetKey(keyRef string) SetKeyRing(keyring, secretKeyring string) SetPassphrase(passphrase, passphraseFile string) + SetBatch(batch bool) DetachedSign(source string, destination string) error ClearSign(source string, destination string) error } @@ -42,6 +43,12 @@ type GpgSigner struct { keyRef string keyring, secretKeyring string passphrase, passphraseFile string + batch bool +} + +// SetBatch control --no-tty flag to gpg +func (g *GpgSigner) SetBatch(batch bool) { + g.batch = batch } // SetKey sets key ID to use when signing files @@ -72,6 +79,10 @@ func (g *GpgSigner) gpgArgs() []string { args = append(args, "-u", g.keyRef) } + if g.passphrase != "" || g.passphraseFile != "" { + args = append(args, "--no-use-agent") + } + if g.passphrase != "" { args = append(args, "--passphrase", g.passphrase) } @@ -80,6 +91,10 @@ func (g *GpgSigner) gpgArgs() []string { args = append(args, "--passphrase-file", g.passphraseFile) } + if g.batch { + args = append(args, "--no-tty") + } + return args } @@ -142,7 +157,7 @@ func (g *GpgVerifier) InitKeyring() error { if err == nil && len(output) == 0 { fmt.Printf("\nLooks like your keyring with trusted keys is empty. You might consider importing some keys.\n") fmt.Printf("If you're running Debian or Ubuntu, it's a good idea to import current archive keys by running:\n\n") - fmt.Printf(" gpg --keyring /usr/share/keyrings/debian-archive-keyring.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n") + fmt.Printf(" gpg --no-default-keyring --keyring /usr/share/keyrings/debian-archive-keyring.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n") fmt.Printf("\n(for Ubuntu, use /usr/share/keyrings/ubuntu-archive-keyring.gpg)\n\n") } } @@ -203,7 +218,7 @@ func (g *GpgVerifier) runGpgv(args []string, context string) error { strings.Join(keyIDs, " ")) fmt.Printf("Sometimes keys are stored in repository root in file named Release.key, to import such key:\n\n") - fmt.Printf("wget -O - http://some.repo/repository/Release.key | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n\n") + fmt.Printf("wget -O - https://some.repo/repository/Release.key | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n\n") } return fmt.Errorf("verification of %s failed: %s", context, err) } diff --git a/src/github.com/smira/aptly/utils/human.go b/src/github.com/smira/aptly/utils/human.go index 8f2eeaff..3a6f9aca 100644 --- a/src/github.com/smira/aptly/utils/human.go +++ b/src/github.com/smira/aptly/utils/human.go @@ -8,13 +8,13 @@ import ( func HumanBytes(i int64) (result string) { switch { case i > (512 * 1024 * 1024 * 1024): - result = fmt.Sprintf("%#.02f TiB", float64(i)/1024/1024/1024/1024) + result = fmt.Sprintf("%.02f TiB", float64(i)/1024/1024/1024/1024) case i > (512 * 1024 * 1024): - result = fmt.Sprintf("%#.02f GiB", float64(i)/1024/1024/1024) + result = fmt.Sprintf("%.02f GiB", float64(i)/1024/1024/1024) case i > (512 * 1024): - result = fmt.Sprintf("%#.02f MiB", float64(i)/1024/1024) + result = fmt.Sprintf("%.02f MiB", float64(i)/1024/1024) case i > 512: - result = fmt.Sprintf("%#.02f KiB", float64(i)/1024) + result = fmt.Sprintf("%.02f KiB", float64(i)/1024) default: result = fmt.Sprintf("%d B", i) } diff --git a/src/github.com/smira/aptly/utils/human_test.go b/src/github.com/smira/aptly/utils/human_test.go index 3be6d385..9e54bafc 100644 --- a/src/github.com/smira/aptly/utils/human_test.go +++ b/src/github.com/smira/aptly/utils/human_test.go @@ -1,7 +1,7 @@ package utils import ( - . "launchpad.net/gocheck" + . "gopkg.in/check.v1" ) type HumanSuite struct{} diff --git a/src/github.com/smira/aptly/utils/list_test.go b/src/github.com/smira/aptly/utils/list_test.go index f8165555..fa2114ae 100644 --- a/src/github.com/smira/aptly/utils/list_test.go +++ b/src/github.com/smira/aptly/utils/list_test.go @@ -1,7 +1,7 @@ package utils import ( - . "launchpad.net/gocheck" + . "gopkg.in/check.v1" ) type ListSuite struct { diff --git a/src/github.com/smira/aptly/utils/utils_test.go b/src/github.com/smira/aptly/utils/utils_test.go index 164a64ef..65cd6706 100644 --- a/src/github.com/smira/aptly/utils/utils_test.go +++ b/src/github.com/smira/aptly/utils/utils_test.go @@ -1,8 +1,9 @@ package utils import ( - . "launchpad.net/gocheck" "testing" + + . "gopkg.in/check.v1" ) // Launch gocheck tests