Git Product home page Git Product logo

bmaptool's Introduction

bmaptool

The better dd for embedded projects, based on block maps.

Introduction

bmaptool is a generic tool for creating the block map (bmap) for a file and copying files using the block map. The idea is that large files, like raw system image files, can be copied or flashed a lot faster and more reliably with bmaptool than with traditional tools, like dd or cp.

bmaptool was originally created for the "Tizen IVI" project and it was used for flashing system images to USB sticks and other block devices. bmaptool can also be used for general image flashing purposes, for example, flashing Fedora Linux OS distribution images to USB sticks.

Originally Tizen IVI images had been flashed using the dd tool, but bmaptool brought a number of advantages.

  • Faster. Depending on various factors, like write speed, image size, how full is the image, and so on, bmaptool was 5-7 times faster than dd in the Tizen IVI project.
  • Integrity. bmaptool verifies data integrity while flashing, which means that possible data corruptions will be noticed immediately.
  • Usability. bmaptool can read images directly from the remote server, so users do not have to download images and save them locally.
  • Protects user's data. Unlike dd, if you make a mistake and specify a wrong block device name, bmaptool will less likely destroy your data because it has protection mechanisms which, for example, prevent bmaptool from writing to a mounted block device.

Usage

bmaptool supports 2 subcommands:

  • copy - copy a file to another file using bmap or flash an image to a block device
  • create - create a bmap for a file

You can get usage reference for bmaptool and all the supported command using the -h or --help options:

$ bmaptool -h # General bmaptool help
$ bmaptool <cmd> -h # Help on the <cmd> sub-command

You can also refer to the bmaptool manual page:

$ man bmaptool

Concept

This section provides general information about the block map (bmap) necessary for understanding how bmaptool works. The structure of the section is:

  • "Sparse files" - the bmap ideas are based on sparse files, so it is important to understand what sparse files are.
  • "The block map" - explains what bmap is.
  • "Raw images" - the main usage scenario for bmaptool is flashing raw images, which this section discusses.
  • "Usage scenarios" - describes various possible bmap and bmaptool usage scenarios.

Sparse files

One of the main roles of a filesystem, generally speaking, is to map blocks of file data to disk sectors. Different file-systems do this mapping differently, and filesystem performance largely depends on how well the filesystem can do the mapping. The filesystem block size is usually 4KiB, but may also be 8KiB or larger.

Obviously, to implement the mapping, the file-system has to maintain some kind of on-disk index. For any file on the file-system, and any offset within the file, the index allows you to find the corresponding disk sector, which stores the file's data. Whenever we write to a file, the filesystem looks up the index and writes to the corresponding disk sectors. Sometimes the filesystem has to allocate new disk sectors and update the index (such as when appending data to the file). The filesystem index is sometimes referred to as the "filesystem metadata".

What happens if a file area is not mapped to any disk sectors? Is this possible? The answer is yes. It is possible and these unmapped areas are often called "holes". And those files which have holes are often called "sparse files".

All reasonable file-systems like Linux ext[234], btrfs, XFS, or Solaris XFS, and even Windows' NTFS, support sparse files. Old and less reasonable filesystems, like FAT, do not support holes.

Reading holes returns zeroes. Writing to a hole causes the filesystem to allocate disk sectors for the corresponding blocks. Here is how you can create a 4GiB file with all blocks unmapped, which means that the file consists of a huge 4GiB hole:

$ truncate -s 4G image.raw
$ stat image.raw
  File: image.raw
  Size: 4294967296   Blocks: 0     IO Block: 4096   regular file

Notice that image.raw is a 4GiB file, which occupies 0 blocks on the disk! So, the entire file's contents are not mapped anywhere. Reading this file would result in reading 4GiB of zeroes. If you write to the middle of the image.raw file, you'll end up with 2 holes and a mapped area in the middle.

Therefore:

  • Sparse files are files with holes.
  • Sparse files help save disk space, because, roughly speaking, holes do not occupy disk space.
  • A hole is an unmapped area of a file, meaning that it is not mapped anywhere on the disk.
  • Reading data from a hole returns zeroes.
  • Writing data to a hole destroys it by forcing the filesystem to map corresponding file areas to disk sectors.
  • Filesystems usually operate with blocks, so sizes and offsets of holes are aligned to the block boundary.

It is also useful to know that you should work with sparse files carefully. It is easy to accidentally expand a sparse file, that is, to map all holes to zero-filled disk areas. For example, scp always expands sparse files, the tar and rsync tools do the same, by default, unless you use the --sparse option. Compressing and then decompressing a sparse file usually expands it.

There are 2 ioctl's in Linux which allow you to find mapped and unmapped areas: FIBMAP and FIEMAP. The former is very old and is probably supported by all Linux systems, but it is rather limited and requires root privileges. The latter is a lot more advanced and does not require root privileges, but it is relatively new (added in Linux kernel, version 2.6.28).

Recent versions of the Linux kernel (starting from 3.1) also support the SEEK_HOLE and SEEK_DATA values for the whence argument of the standard lseek() system call. They allow positioning to the next hole and the next mapped area of the file.

Advanced Linux filesystems, in modern kernels, also allow "punching holes", meaning that it is possible to unmap any aligned area and turn it into a hole. This is implemented using the FALLOC_FL_PUNCH_HOLE mode of the fallocate() system call.

The bmap

The bmap is an XML file, which contains a list of mapped areas, plus some additional information about the file it was created for, for example:

  • SHA256 checksum of the bmap file itself
  • SHA256 checksum of the mapped areas
  • the original file size
  • amount of mapped data

The bmap file is designed to be both easily machine-readable and human-readable. All the machine-readable information is provided by XML tags. The human-oriented information is in XML comments, which explain the meaning of XML tags and provide useful information like amount of mapped data in percent and in MiB or GiB.

So, the best way to understand bmap is to just to read it. Here is an example of a bmap file.

Raw images

Raw images are the simplest type of system images which may be flashed to the target block device, block-by-block, without any further processing. Raw images just "mirror" the target block device: they usually start with the MBR sector. There is a partition table at the beginning of the image and one or more partitions containing filesystems, like ext4. Usually, no special tools are required to flash a raw image to the target block device. The standard dd command can do the job:

$ dd if=tizen-ivi-image.raw of=/dev/usb_stick

At first glance, raw images do not look very appealing because they are large and it takes a lot of time to flash them. However, with bmap, raw images become a much more attractive type of image. We will demonstrate this, using Tizen IVI as an example.

The Tizen IVI project uses raw images which take 3.7GiB in Tizen IVI 2.0 alpha. The images are created by the MIC tool. Here is a brief description of how MIC creates them:

  • create a 3.7GiB sparse file, which will become the Tizen IVI image in the end
  • partition the file using the parted tool
  • format the partitions using the mkfs.ext4 tool
  • loop-back mount all the partitions
  • install all the required packages to the partitions: copy all the needed files and do all the tweaks
  • unmount all loop-back-mounted image partitions, the image is ready
  • generate the block map file for the image
  • compress the image using bzip2, turning them into a small file, around 300MiB

The Tizen IVI raw images are initially sparse files. All the mapped blocks represent useful data and all the holes represent unused regions, which "contain" zeroes and do not have to be copied when flashing the image. Although information about holes is lost once the image gets compressed, the bmap file still has it and it can be used to reconstruct the uncompressed image or to flash the image quickly, by copying only the mapped regions.

Raw images compress extremely well because the holes are essentially zeroes, which compress perfectly. This is why 3.7GiB Tizen IVI raw images, which contain about 1.1GiB of mapped blocks, take only 300MiB in a compressed form. And the important point is that you need to decompress them only while flashing. The bmaptool does this "on-the-fly".

Therefore:

  • raw images are distributed in a compressed form, and they are almost as small as a tarball (that includes all the data the image would take)
  • the bmap file and the bmaptool make it possible to quickly flash the compressed raw image to the target block device
  • optionally, the bmaptool can reconstruct the original uncompressed sparse raw image file

And, what is even more important, is that flashing raw images is extremely fast because you write directly to the block device, and write sequentially.

Another great thing about raw images is that they may be 100% ready-to-go and all you need to do is to put the image on your device "as-is". You do not have to know the image format, which partitions and filesystems it contains, etc. This is simple and robust.

Usage scenarios

Flashing or copying large images is the main bmaptool use case. The idea is that if you have a raw image file and its bmap, you can flash it to a device by writing only the mapped blocks and skipping the unmapped blocks.

What this basically means is that with bmap it is not necessary to try to minimize the raw image size by making the partitions small, which would require resizing them. The image can contain huge multi-gigabyte partitions, just like the target device requires. The image will then be a huge sparse file, with little mapped data. And because unmapped areas "contain" zeroes, the huge image will compress extremely well, so the huge image will be very small in compressed form. It can then be distributed in compressed form, and flashed very quickly with bmaptool and the bmap file, because bmaptool will decompress the image on-the-fly and write only mapped areas.

The additional benefit of using bmap for flashing is the checksum verification. Indeed, the bmaptool create command generates SHA256 checksums for all mapped block ranges, and the bmaptool copy command verifies the checksums while writing. Integrity of the bmap file itself is also protected by a SHA256 checksum and bmaptool verifies it before starting flashing.

On top of this, the bmap file can be signed using OpenPGP (gpg) and bmaptool automatically verifies the signature if it is present. This allows for verifying the bmap file integrity and authoring. And since the bmap file contains SHA256 checksums for all the mapped image data, the bmap file signature verification should be enough to guarantee integrity and authoring of the image file.

The second usage scenario is reconstructing sparse files Generally speaking, if you had a sparse file but then expanded it, there is no way to reconstruct it. In some cases, something like:

$ cp --sparse=always expanded.file reconstructed.file

would be enough. However, a file reconstructed this way will not necessarily be the same as the original sparse file. The original sparse file could have contained mapped blocks filled with all zeroes (not holes), and, in the reconstructed file, these blocks will become holes. In some cases, this does not matter. For example, if you just want to save disk space. However, for raw images, flashing it does matter, because it is essential to write zero-filled blocks and not skip them. Indeed, if you do not write the zero-filled block to corresponding disk sectors which, presumably, contain garbage, you end up with garbage in those blocks. In other words, when we are talking about flashing raw images, the difference between zero-filled blocks and holes in the original image is essential because zero-filled blocks are the required blocks which are expected to contain zeroes, while holes are just unneeded blocks with no expectations regarding the contents.

bmaptool may be helpful for reconstructing sparse files properly. Before the sparse file is expanded, you should generate its bmap (for example, by using the bmaptool create command). Then you may compress your file or, otherwise, expand it. Later on, you may reconstruct it using the bmaptool copy command.

Known Issues

ZFS File System

If running on the ZFS file system, the Linux ZFS kernel driver parameters configuration can cause the finding of mapped and unmapped areas to fail. This can be fixed temporarily by doing the following:

$ echo 1 | sudo tee -a /sys/module/zfs/parameters/zfs_dmu_offset_next_sync

However, if a permanent solution is required then perform the following:

$ echo "options zfs zfs_dmu_offset_next_sync=1" | sudo tee -a /etc/modprobe.d/zfs.conf

Depending upon your Linux distro, you may also need to do the following to ensure that the permanent change is updated in all your initramfs images:

$ sudo update-initramfs -u -k all

To verify the temporary or permanent change has worked you can use the following which should return 1:

$ cat /sys/module/zfs/parameters/zfs_dmu_offset_next_sync

More details can be found in the OpenZFS documentation.

Hacking

bmaptool uses hatch to build python packages. If you would like to make changes to the project, first create a new virtual environment and activate it:

python3 -m venv .venv
. .venv/bin/activate

Next install the project in editable mode with development dependencies:

pip install -e '.[dev]'

Note: You may need to install the development package for libgpgme on your system. Depending on your OS this may be called libgpgme-dev.

Finally, to run tests use unittest:

python3 -m unittest -bv

Project and maintainer

The bmaptool project implements bmap-related tools and API modules. The entire project is written in python.

The project author is Artem Bityutskiy ([email protected]). The project is currently maintained by:

Project git repository is here: https://github.com/yoctoproject/bmaptool

Artem's Credits

  • Ed Bartosh ([email protected]) for helping me with learning python (this is my first python project) and working with the Tizen IVI infrastructure. Ed also implemented the packaging.
  • Alexander Kanevskiy ([email protected]) and Kevin Wang ([email protected]) for helping with integrating this stuff to the Tizen IVI infrastructure.
  • Simon McVittie ([email protected]) for improving Debian packaging and fixing bmaptool.

bmaptool's People

Contributors

dedekind avatar smcv avatar kad avatar jpewdev avatar mntns avatar twoerner avatar schwesinger avatar lurch avatar dellgreen avatar ronan22 avatar bart0sh avatar zxcv1884 avatar pascaleberhard0se avatar ribalda avatar chrthi-work avatar sjoerdsimons avatar tcler avatar moto-timo avatar mythi avatar mk-fg avatar gschwaer avatar deepcube avatar diegorondini avatar bwildenhain avatar andy-shev avatar agherzan avatar aekoroglu avatar akiernan avatar

Stargazers

Devin Cofer avatar  avatar  avatar Arnab Bag avatar  avatar Börje Sewing avatar Andrew Morris avatar Marius Kriegerowski avatar Taylor Braun-Jones avatar Christian <kimo> B. avatar Joel Purra avatar  avatar Devendra Tewari avatar Philipp Huth avatar

Watchers

 avatar Philipp Huth avatar  avatar

bmaptool's Issues

Documentation link is broken

Hi,

The documentation link source.tizen.org/documentation/reference/bmaptool from the help message is broken. It leads to the "Introduction to Tizen" page.

bmaptool --help
usage: bmaptool [-h] [--version] [-q] [-d] {create,copy} ...

A tool for creating block maps (bmap) and copying disk images using bmap files. Documentation can be found here:
source.tizen.org/documentation/reference/bmaptool

optional arguments:
  -h, --help     show this help message and exit
  --version      show program's version number and exit
  -q, --quiet    be quiet
  -d, --debug    print debugging information

commands:
  {create,copy}
    create       generate bmap for an image file (which should be a sparse file)
    copy         write an image to a block device using bmap

mismatch between implementation and documentation of bmap path discovery strategy

The documentation states:

bmaptool automatically discovers it by looking for a file with the same basename as IMAGE but with the ".bmap" extension.

Lets try this out:

$ echo foo > foo.img
$ echo bar > bar.img
$ bmaptool create foo.img > foo.bmap
$ bmaptool create bar.img > foo.img.bmap
$ bmaptool copy foo.img disk
bmaptool: info: discovered bmap file 'foo.img.bmap'
bmaptool: info: block map format version 2.0
bmaptool: info: 1 blocks of size 4096 (4 bytes), mapped 1 blocks (4.0 KiB or 100.0%)
bmaptool: info: copying image 'foo.img' to file 'disk' using bmap file 'foo.img.bmap'
bmaptool: info: 100% copied
bmaptool: ERROR: An error occurred, here is the traceback:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/bmaptools/CLI.py", line 490, in copy_command
    writer.copy(False, not args.no_verify)
  File "/usr/lib/python3/dist-packages/bmaptools/BmapCopy.py", line 617, in copy
    reraise(exc_info[0], exc_info[1], exc_info[2])
  File "/usr/lib/python3/dist-packages/six.py", line 719, in reraise
    raise value
  File "/usr/lib/python3/dist-packages/bmaptools/BmapCopy.py", line 562, in _get_data
    raise Error("checksum mismatch for blocks range %d-%d: "

bmaptool: ERROR: checksum mismatch for blocks range 0-0: calculated b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c, should be 7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730 (image file foo.img)

This happens because instead of looking for a bmap file by taking the basename of the image file and adding the bmap extension, bmaptool first tries the original filename (not its basename) and adds the bmap extension to that.

Where is the error? In the documentation or in the implementation?

It becomes even more confusing if the file has multiple extensions which is common when compressing the disk image. For example, if the image file is called foo.img.gz, then bmaptool will look for bmap files in this order:

  1. foo.img.gz.bmap
  2. foo.img.bmap
  3. foo.bmap

Is this intentional and should the documentation be updated or should the implementation be changed to match the documentation?

Thanks!

installation fails because libgpgme cannot be found

I ran apt install libgpgme-dev which installed as expected.
pip install fails with:

Collecting gpg>=1.10.0
  Using cached gpg-1.10.0.tar.gz (39 kB)
  Preparing metadata (setup.py) ... error
  error: subprocess-exited-with-error

  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [1 lines of output]
      Could not find gpgme-config.  Please install the libgpgme development package.
      [end of output]

On python 3.11.2

No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm

RfC: support for older Debian releases (before bookworm)

Somehow this does not affect Ubuntu focal+ which seems to build fine with all the project data in pyproject.toml, however, the github CI build for debian release fails to populate the package correctly on anything older than bookworm (using this action to build packages in a docker container).

Until things "catch up" as far as support for toml metadata the only workaround i know is a patch to move the metadata back to setup.cfg, which is fully supported for python 3.x up to current versions. The nice thing is setup.cfg is much more readable (than toml) but the not-so-nice thing is author/maintainer fields can only hold a string.

This is just something to think about for a bit.

improvement: option to disable bmap on specific ranges

Hello,

Would it be possible to have an option specifying ranges for which all zeros would be copied ?

In my use case I have a wic image with a verity rootfs on the 3rd partition (let's say from bytes START to END). I would appreciate an option:

bmaptool create --nomap START:END [...]

so the mapping does not skip any zero between START and END.

treatment of on-disk segments as "what was written by programs" can cause areas of `0` to not be written by `bmaptool copy`

see intel#75 for the original issue. Migrating here because all issues on that repo were closed.

Content of a comment I left explaining the issue:

  1. I (well, wic internally) was using bmaptool create /file/on/zfs/disk.img.
  2. The target file (on zfs) stores a full disk image, which included some squashfs & raw binary images (but could include anything)
  3. When using the bmap created by the above, the raw images which were all zero bytes were not written when using bmaptool copy (with the bmap generated from the disk.img that was formed on the zfs filesystem).

So:

  1. It's entirely possible other segments of the file that are written to zero (or determined to be zero by reading and not written) by, for example, file system generators (squashfs) or filesystem drivers mounted in loopback (ext4, etc) would also not be captured by a bmaptool create, and those zero'd segments would then not be copied by bmaptool copy.
  2. bmaptool essentially wants to distinguish between "don't care" bytes and all other bytes, but the filesystem apis in use (FIEMAP, seek HOLE/DATA) don't expose this info, they only expose runs of zeros which may or may not be "don't care" bytes.
  3. To resolve this, bmaptool probably needs to do something like have a fuse fs or blockdev or a network blockdev which tracks reads/writes to a file to determine which bytes are actually don't cares (never read or written). Determining this after the fact generally is reliant on filesystem driver/generator implementation details.
  4. Even a fuse fs (or other mechanism to capture all reads/writes) may not be sufficient for filesystem generator programs which know they are creating a new file and thus may presume default content for unwritten regions of that file. iow: one would need to examine the implementation of fs generator programs (those which take a filesystem tree and generate a block image file) to ensure they don't include those assumptions (and to be clear: assuming things about unwritten space in a new file is permitted by the standards, so having them not make that assumption to support bmaptool would be an additional requirement).

To use bmaptool successfully today, one needs to audit both the filesystem driver (and vfs, etc) for where they are storing the image file they want to run bmaptool create on, and they also need to audit all the programs/drivers/etc they're using to write data into that file to ensure the expected behavior (that holes/unallocated regions always correspond to "don't care" bytes).

bmaptool does not work on ecryptfs

Hi,

My home partition is encrypted with ecryptfs

$ mount | grep /home
/home/.ecryptfs/gschwaer/.Private on /home/gschwaer type ecryptfs (rw,nosuid,nodev,relatime,ecryptfs_fnek_sig=1234567890123456,ecryptfs_sig=1234567890123456,ecryptfs_cipher=aes,ecryptfs_key_bytes=16,ecryptfs_unlink_sigs)

When I try to run bmaptool like so:

cd ~/.tmp
git clone [email protected]:yoctoproject/bmaptool.git
cd bmaptool
python3.9 -m venv .venv
source .venv/bin/activate
pip install .[dev]
bmaptool create README.md

I get the following error:

bmaptool create README.md 
bmaptool: ERROR: An error occurred, here is the traceback:
Traceback (most recent call last):
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 412, in _invoke_fiemap
    fcntl.ioctl(self._f_image, _FIEMAP_IOCTL, self._buf, 1)
OSError: [Errno 95] Operation not supported

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 568, in filemap
    return FilemapFiemap(image)
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 376, in __init__
    self.block_is_mapped(0)
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 437, in block_is_mapped
    struct_fiemap = self._invoke_fiemap(block, 1)
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 422, in _invoke_fiemap
    raise ErrorNotSupp(errstr)
bmaptool.Filemap.ErrorNotSupp: FilemapFiemap: the FIEMAP ioctl is not supported by the file-system

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/BmapCreate.py", line 177, in __init__
    self.filemap = Filemap.filemap(self._f_image)
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 570, in filemap
    return FilemapSeek(image)
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 216, in __init__
    self._probe_seek_hole()
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/Filemap.py", line 253, in _probe_seek_hole
    raise ErrorNotSupp(
bmaptool.Filemap.ErrorNotSupp: the file-system does not support "SEEK_HOLE" and "SEEK_DATA" but only provides a stub implementation

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/CLI.py", line 640, in create_command
    creator = BmapCreate.BmapCreate(args.image, output, "sha256")
  File "/home/gschwaer/.tmp/bmaptool/.venv/lib/python3.9/site-packages/bmaptool/BmapCreate.py", line 179, in __init__
    raise Error(

bmaptool: ERROR: cannot generate bmap for file 'README.md': the file-system does not support "SEEK_HOLE" and "SEEK_DATA" but only provides a stub implementation

Is this expected behavior? Or is it an issue with my ecryptfs setup?

Versions:

  • bmaptool: main
  • uname -orm: 5.15.0-102-generic x86_64 GNU/Linux
  • ecryptfs: (builtin)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.