Puis-je sauvegarder ces documents sur une machine en train de mourir de l'oubli?


49

Tout d'abord, une confession: non, je n'ai pas fait les sauvegardes que je devrais avoir.

Deuxièmement, la situation:

J'ai un Dell XPS 9550 avec un disque SSD exécutant Fedora 25 .

Je travaillais sur un fichier et j'ai essayé de l'enregistrer quand on m'a dit que j'essayais de l'enregistrer sur un système de fichiers en lecture seule . Il se trouve que mon système de fichiers est en lecture seule à présent et qu'il y a des erreurs d'E / S partout.

J'ai pu sauvegarder une partie des fichiers en les envoyant par courrier électronique via un navigateur Web ouvert, mais cela s'est bloqué et je suis incapable de le relancer. Mais j'ai encore des dossiers d'intérêt ouverts dans un éditeur. Je n'arrive pas à enregistrer les fichiers où que ce soit, mais je peux en copier le contenu. Si seulement je pouvais trouver un moyen d’exfiltrer le contenu du fichier, je pourrais économiser un mois de travail.

Mais il y a des limites horribles. J'ai essayé d'insérer un lecteur USB, mais aucun périphérique ne semble le représenter et la mountcommande meurt avec un segfault. Je peux essayer de ssh sur un autre ordinateur, mais je reçois une "erreur de bus" et il meurt. ping, dmesg, ifconfig, Aucun de ces travaux. Mais j'ai vimet lesset lset peut générer de nouvelles bashinstances.

Non lynx, non firefox, non google-chrome. Il n'y a pas de lecteur de DVD.

Fondamentalement, il semble que mon disque SSD soit mort. Ou peut-être toute la carte mère. J'ai des documents de grande valeur encore en mémoire, j'ai une adresse IP et une connexion réseau, je peux exécuter quelques commandes aléatoires et 3500 autres sur le chemin que je pourrais essayer.

catet gccsemblent fonctionner. Je peux écrire dans des fichiers dans / tmp. J'ai une ipythoninstance en cours d'exécution qui semble toujours fonctionner.

Alors ... ce que j'ai essayé jusqu'à présent a échoué. Mais j'ai l'impression qu'il y a encore mille possibilités. Qu'est-ce que je n'envisage pas? Comment pourrais-je éventuellement récupérer ces fichiers de mon ordinateur en train de mourir?

Il doit y avoir un moyen.

MISE À JOUR : nouveautés:

  • J'ai perdu ma connexion réseau à cause de ma propre stupidité.
  • J'ai écrit un script Python pour remplacer cpetcp -r
  • À moins que je ne trouve un moyen de créer une /deventrée pour la carte SD ou pour les clés USB, mes meilleurs atouts pour extraire des données semblent être l'écran et éventuellement le câble haut-parleur / audio.
  • J'écris un script pour essayer de lire des fichiers et de sortir ceux qui sont lisibles.

Les suggestions sont toujours les bienvenues!

MISE À JOUR 2 : Nouveautés récentes:

  • Sur l'ordinateur mourant, j'ai écrit un script Python qui lit un fichier à la fois et essaie de transmettre ces bits en faisant clignoter l'écran d'une couleur ou d'une autre. En ce moment, on essaie de créer un code à deux bits où rouge, vert, bleu et blanc représentent tous une paire de deux bits. Cela ne fonctionne pas très bien, cependant, je pourrais simplement passer à deux couleurs et faire un peu à la fois.
  • Sur mon autre ordinateur portable (le vieux Thinkpad, que j'ai abandonné pour ce nouveau XPS), j'ai écrit un script qui lit à partir de la webcam à l'aide de la bibliothèque OpenCV Python. L'idée est de le décoder les codes envoyés par l'autre ordinateur. Le problème, c'est que le framerate de l'appareil photo est d'environ 15 images par seconde, ce qui signifie que si j'avais un transfert parfait et sans erreur, mon débit maximum serait de 30 bits par seconde, soit 225 octets par seconde. C'est 324k par jour.
  • Sur le XPS en voie de disparition, je peux utiliser tarpour regrouper les fichiers souhaités dans une archive unique de 1,7 Mo. Malheureusement, gzip, bzip2, xz, lzopet quelles que soient les services publics de compression ne sont pas disponibles. MAIS en utilisant le zlibmodule Python, je peux compresser ce fichier jusqu’à 820 Ko. Compte tenu de cette taille, je pourrais probablement recevoir cette chose dans quelques jours.
  • Étant donné que cette méthode de transfert sera probablement très sujette aux erreurs, je vais implémenter les codes de Hamming sur le système XPS afin d'ajouter une correction d'erreur lors de la transmission des données.
  • Il y aura probablement des complications parce que c'est ce qui se produit, mais au moins, il semble en quelque sorte faisable d'obtenir ces données!
  • Comme cela reste un moyen assez fastidieux d’envoyer des données, je me suis plus intéressé aux pilotes série USB. Les modules que j'ai essayé de charger ( usb-serial-simple, usb-debug, safe-serial) donnent i / o des erreurs. Je ne pense pas non plus qu'il soit intégré au noyau, car il n'y a aucun périphérique / dev / ttyUSB * présent.

Merci pour les suggestions de chacun jusqu'à présent - je sais que ce n'est même pas une question bien définie car vous ne savez pas à l'avance quels programmes / fichiers peuvent être lus ou non. Toujours ouvert à de meilleures suggestions que cette approche vidéo!

UPDATE 3 : Nouveautés

  • J'ai une webcam PS3 Eye et, après avoir désactivé son gain et son exposition automatiques, je lis avec succès les données du XPS, bien qu'un taux erroné de 1 octet par seconde. C’est un grand succès: les premières données ont été exfiltrées! Mais le taux est trop lent pour obtenir mes 820 Ko dans un délai raisonnable, et le taux d'erreur est trop élevé.

Transmission à un bit avec horloge

  • Le problème est que l'écriture sur le terminal est trop lente. Les mises à jour d'écran ne sont pas instantanées, merci (je pense) à la lenteur de l' urxvtémulateur de terminal auquel j'ai accès.
  • J'ai découvert que j'ai accès à un compilateur Rust sur XPS. J'ai réécrit le script de transmission en utilisant Rust pour voir si cela améliorerait la vitesse d'actualisation du terminal, mais cela n'a pas aidé.
  • Comme il est peu probable que je puisse augmenter le nombre d'images par seconde, je dois essayer d'augmenter la quantité de données que j'obtiens par image. Mon approche actuelle ressemble à ceci:

transmission par grille

La moitié droite est toujours un signal d'horloge, clignotant pour marquer l'arrivée de nouvelles images. Mais la gauche est maintenant une grille où chaque cellule est marquée par un carré rouge dans le coin, puis la cellule verte à droite et en bas du carré rouge est clignotante pour indiquer un bit. Les carrés rouges doivent laisser l’ordinateur récepteur se calibrer à l’emplacement des cellules. Je n'ai pas encore de données, mais c'est ce sur quoi je travaille.

  • Quelqu'un a suggéré que je cherche à écrire des codes QR au lieu de ces motifs de couleur ad hoc. Je vais aussi examiner cela, et peut-être mettre en place cette approche au lieu de cette approche par grille. La correction d'erreur serait une belle victoire, tout en permettant d'utiliser des bibliothèques standard pour décoder.
  • J'ai appris que j'avais accès à libasound (la bibliothèque de sons ALSA), mais pas aux fichiers d'en-tête qui lui étaient associés ( alsa/asoundlib.hou quoi que ce soit d'autre). Si quelqu'un sait comment utiliser une bibliothèque partagée sans en-têtes, ou s'il peut m'aider à écrire le bon en-tête pour me permettre de produire une sortie audio, alors je pourrais avoir un moyen audio de sortir les fichiers.
  • Sinon, si quelqu'un pouvait m'aider à manipuler les périphériques USB sans accès à libusb, je pourrais peut-être faire quelque chose avec ça?

Avancer!

UPDATE 4 : sortie audio produite!

L’utilisateur Francesco Noferi a réalisé un travail remarquable en m'aidant à utiliser la bibliothèque ALSA mentionnée dans la mise à jour précédente. Le compilateur C avait un problème, mais en utilisant le compilateur Rust, je pouvais utiliser le FFI pour appeler directement libasound. J'ai maintenant joué un tas de mes données sur audio et ça sonne comme de la musique à mes oreilles! Encore faut-il établir un véritable canal de communication, mais je me sens très optimiste. À ce stade, mon travail consiste essentiellement à mettre en place un modem. Si quelqu'un a des conseils sur la bonne façon de le faire, je suis tout ouïe. Idéalement, une modulation facile à mettre en œuvre manuellement et une démodulation pour laquelle il existe une bibliothèque que je peux utiliser. Comme cela peut aller directement sur un câble audio et non sur le réseau téléphonique, en théorie nous pouvons faire beaucoup mieux que 56 kbps ou peu importe la norme établie dans la journée, mais en pratique, qui sait ce que nous aurons.

Merci à tous ceux qui nous ont suivis ici et à / r / techsupportmacgyver et à / r / rouille pour leurs excellentes suggestions. Je vais bientôt faire installer ce "modem", puis je terminerai avec un épilogue. Je pense que je pourrais mettre mon code en place quelque part pour que d'autres personnes désespérées puissent l'utiliser à l'avenir --- peut-être même un référentiel d'outils étranges d'exfiltration faciles à taper manuellement dans une machine en train de mourir? Nous allons voir ce qui se passe.

MISE À JOUR 5 : Il m’a fallu beaucoup de temps pour lutter contre ALSA et mon périphérique de capture audio USB StarTech bon marché (aucune ligne intégrée sur le portable de réception), et de nombreux faux lancements tentent d’appliquer mon propre protocole de transmission, mais finalement sous les conseils de certains utilisateurs. Mes amis passionnés de radio Ham ont mis en œuvre le protocole de ligne RTTY fonctionnant à 150 bauds, ce qui en pratique me donne peut-être environ 10 octets par seconde. Ce n'est pas super rapide mais c'est assez fiable. Et j’ai presque fini de transférer mon fichier de 820 Ko, vérifié à l’aide des sommes de contrôle CRC32 (à l’aide de la fonctionnalité crc32 de Python).zlibmodule auquel j'ai accès). Donc, je déclare la victoire et tiens à remercier encore une fois! Je vais passer un peu plus de temps à trouver d'autres fichiers lisibles et que je peux transférer, mais les bases sont en place. Ça fait plaisir de travailler avec vous tous!

MISE À JOUR FINALE :

Sur la machine à mourir:

$ tar cf ./files
$ ./checksum.py ./files.tar 9999999
Part 1 checksum: -1459633665
$ ./zlib_compress.py ./files.tar
$ ./checksum.py ./files.tar.z 9999999
Part 1 checksum: -378365928
$ ./transmit_rust/target/debug/transmit ./files.tar.z
Transmitting files.tar.gz over audio using RTTY
Period size: 2048
Sample rate: 44100
Samples per bit: 294
Sending start signal.
Transmitting data.
nread: 2048
nread: 2048
...
nread: 2048
nread: 208
Transmission complete. Sending hold signal.

Sur la machine de secours:

$ minimodem --rx -8 --rx-one -R 44100 -S 915 -M 1085 --startbits 3
            --stopbits 2 --alsa=1 150 -q > ./files.tar.z
$ ./checksum.py ./files.tar.z
Part 1 checksum: -378365928
$ ./zlib_decompress.py ./files.tar.z
$ ./checksum.py ./files.tar
Part 1 checksum: -1459633665

:-)



2
Allez dans le répertoire où vous avez les fichiers et lancez la commande python -m SimpleHTTPServer. Vous partagez maintenant les fichiers via un serveur http sur le port 8000 . Ouvrez un navigateur sur un autre appareil du même réseau et tapez ce qui suit: http://<IP address>:8000et commencez à télécharger tout ce que vous pouvez.
Jcbermu

2
Super morceau de truc de geek que vous avez ici. J'aimerais pouvoir voter deux fois.
Kamil Maciorowski

1
Pourquoi ne pas refroidir la RAM pour réduire la volatilité et la transférer sur un autre système XPS? fr.wikipedia.org/wiki/Cold_boot_attack
racine

1
Un peu hors sujet, mais ce serait bien si vous pouviez lier les discussions reddit associées afin de voir ce qui a été suggéré.
Bob

Réponses:


15

Voici un exemple de programme libasound avec juste assez de définitions pour obtenir une sortie wav 44.1k de base à 2 canaux sans les en-têtes.

EDIT: En fait, je ne suis pas sûr si le dumping direct des données en tant que wav fonctionnerait, car le bruit lors de l'enregistrement pourrait facilement l'endommager, mais vous pouvez probablement faire quelque chose comme une onde sinusoïdale de bits à haute fréquence qui est plus fiable

EDIT2: si aplay est présent et fonctionne, vous pouvez également l'utiliser et simplement écrire un programme qui génère un fichier audio brut et le transférer dans un fichier ou tout autre élément pouvant lire un fichier audio.

EDIT3: modifié pour ne plus utiliser aucun en-tête

si -lasound ne compile pas, ajoutez -L / chemin / où / libasound / est / est situé

/*
    gcc alsa_noheader.c -lasound
    cat stuff.wav | ./a.out
*/

typedef unsigned int uint;
typedef unsigned long ulon;

int printf(char*, ...);
void* malloc(long);
long read(int fd, void* buf, ulon count);

int snd_pcm_open(void**, char*, int, int);
ulon snd_pcm_hw_params_sizeof();
int snd_pcm_hw_params_any(void*, void*);
int snd_pcm_hw_params_set_access(void*, void*, int);
int snd_pcm_hw_params_set_format(void*, void*, int);
int snd_pcm_hw_params_set_channels(void*, void*, uint);
int snd_pcm_hw_params_set_rate_near(void*, void*, uint*, int*);
int snd_pcm_hw_params(void*, void*);
int snd_pcm_hw_params_get_period_size(void*, ulon*, int*);
long snd_pcm_writei(void*, void*, uint);
int snd_pcm_prepare(void*);
int snd_pcm_drain(void*);
int snd_pcm_close(void*);

int main(int argc, char* argv[])
{
    void* pcm;
    void* params;

    int rate;
    int nchannels;
    ulon frames;
    void* buf;
    int bufsize;
    long nread;

    snd_pcm_open(&pcm, "default", 0, 0);
    params = malloc(snd_pcm_hw_params_sizeof());
    snd_pcm_hw_params_any(pcm, params);

    /* 3 = rw_interleaved */
    snd_pcm_hw_params_set_access(pcm, params, 3);

    /* 2 = 16-bit signed little endian */
    snd_pcm_hw_params_set_format(pcm, params, 2);

    /* 2 channels */
    nchannels = 2;
    snd_pcm_hw_params_set_channels(pcm, params, nchannels);

    /* sample rate */
    rate = 44100;
    snd_pcm_hw_params_set_rate_near(pcm, params, &rate, 0);

    snd_pcm_hw_params(pcm, params);
    snd_pcm_hw_params_get_period_size(params, &frames, 0);

    bufsize = frames * nchannels * 2;
    buf = malloc(bufsize);

    /* read file from stdin */
    while (nread = read(0, buf, bufsize) > 0)
    {
        if (snd_pcm_writei(pcm, buf, frames) == -29)
        {
            printf("W: underrun\n");
            snd_pcm_prepare(pcm);
        }
    }

    snd_pcm_drain(pcm);
    snd_pcm_close(pcm);

    return 0;
}

Je vous ai voté et je pense que vous méritez des tonnes de crédit pour cette approche géniale! Voici le problème: je reçois des erreurs d'E / S sur les quatre fichiers d'inclusion que vous souhaitez utiliser. J'ai donc besoin d'un moyen de contourner cela. Je peux faire le prétraitement sur l’autre ordinateur portable, mais cela donne un fichier 63K, 11K compressé. Si je peux trouver un moyen d’obtenir des données dans le XPS, c’est faisable, mais je ne suis pas enthousiaste à l'idée de la saisir manuellement. Le fichier compilé est de 10,4 Ko ou 2,4 Ko compressé. Cela me permet de taper à la main, mais les erreurs seraient difficiles à détecter.
Josh Hansen

1
J'ai modifié le programme pour ne pas utiliser d'en-tête du tout, essayez-le
Francesco Noferi

gccm'a donné une "erreur de bus" (qui contient certains programmes mais ne comprend pas vraiment.) Heureusement, j'ai un compilateur Rust qui fonctionne, aussi, en utilisant la capacité FFI de Rust, j'ai implémenté votre code ci-dessus en utilisant Rust. Il compile et fonctionne, mais je n'entends toujours rien. Mais je ne suis pas vraiment sûr que le volume soit monté - il est peut-être désactivé. Mais je ne peux pas lancer alsamixer / xfce4-mixer pour vérifier. Je cherche à utiliser alsalib directement pour m'assurer que le volume est en hausse. Merci beaucoup pour vos efforts!
Josh Hansen

1
ça fait plaisir de l'entendre courir! pour le volume, voici un programme qui énumère les commandes de mixage du périphérique par défaut et les maximise: gist.github.com/008f0c3acdbcae886a19868a0554987b . Si vous rencontrez toujours des problèmes, vous devrez peut-être écrire quelque chose pour énumérer les sorties audio et leurs entrées de mixage afin de vous assurer que le disque est vraiment en panne et qu'il ne s'agit pas uniquement d'une sortie sur le mauvais périphérique / muet. Vous pouvez également essayer d’ajouter une vérification d’erreur au programme de sortie audio pour voir s’il s’agit d’une erreur. erreur de bus signifie "essayé d'accéder à de la mémoire qui ne pourrait pas être là" alors peut-être corrompu
lib

En fait, le volume était trop élevé, mais je n’envoyais pas réellement d’octets aux haut-parleurs - d’une manière ou d’une autre, je ne comprends pas comment la lecture est effectuée dans un tampon dans Rust, il lit toujours 0 octet. Mais en utilisant quelques octets que j'ai construits, j'ai pu produire une sortie audio! C’est formidable, cela semble être un bon moyen de récupérer les données de manière nette et propre. Il est maintenant temps d’en apprendre davantage sur les stratégies de modulation / démodulation afin que je puisse le faire intelligemment. Merci beaucoup pour votre travail sur ce - très apprécié!
Josh Hansen

3

Votre port HDMI ou tout autre port de sortie d'affichage fonctionne-t-il? Si tel est le cas, vous pouvez utiliser un périphérique de capture d'écran pour l'enregistrer en tant que vidéo et le traiter ultérieurement. Donc, ne pas être limité par le framerate de votre webcam.


2

Que diriez-vous de coder vos données au format hexadécimal et de les envoyer page par page au terminal?

Vous pouvez ajouter un préfixe avec le décalage dans le binaire afin de pouvoir facilement régénérer une page (pour une correction manuelle?)

Puis, sur un autre ordinateur, utilisez un logiciel OCR pour numériser les pages.

Un terminal 80x25 donnerait 1000 octets par page (moins d’espace pour le préfixe). Donc, en environ 1000 pages, vous pouvez sortir vos données. Même à une page par seconde, c'est moins de 20 minutes.

Le codage hexadécimal est facile à écrire et fournit également une forme brute de correction d'erreur (il n'y a que 16 symboles valides).


1

pouvez-vous configurer une connexion réseau? ssh est peut-être un peu trop mais si vous pouvez utiliser netcat sur deux machines, vous pourrez peut-être transférer des données. un en mode d'envoi, un en mode d'écoute. Si tout ce que vous avez à faire est de transférer du texte, alors ceci pourrait être une solution.

edit: tant pis, il suffit de lire que vous avez également perdu votre connexion réseau ..


1
Je pense que votre réponse mérite d'être là-haut. ncn'a pas d'autres dépendances qu'une pile réseau en fonctionnement, ce que l'OP avait jusqu'à ce qu'il l'explose. Cela réduit considérablement les chances que l'exécutable ne démarre pas. Si quelqu'un d'autre rencontre un problème similaire, c'est certainement une solution utile.
zneak

0

Vous pourrez peut- être vous envoyer un courrier électronique à partir de la ligne de commande, y compris pour l'envoi de fichiers.

Quelque chose comme:

$ mail -s "Hello World" user@yourmaildomain.com < /tmp/urgentFileToSave.txt

devrait marcher.

Quelques exemples supplémentaires: http://www.binarytides.com/linux-mail-command-examples/


Malheureusement, mailne fait pas partie de la collection (essentiellement aléatoire) de programmes que je peux exécuter. De plus, j'ai perdu ma connexion réseau :-(
Josh Hansen

Aie. OK, tout ce qui me reste, ce sont catles fichiers et prendre des photos avec votre téléphone portable.
mcalex

Cela peut venir à ça!
Josh Hansen

0

Python peut-il accéder à votre port de sortie audio? Vous pouvez essayer de transférer des données en utilisant cela - voir ceci ou cela .


Je ne pense pas que les bibliothèques audio de Python soient installées, donc probablement pas. Mais il y a des périphériques ALSA dans / dev / snd. alsa-lib est installé, mais je ne pense pas pouvoir le lire. Est-il possible d'écrire directement sur les périphériques audio?
Josh Hansen

@ user17219 Êtes-vous sûr qu'il n'y a pas de ossaudiodevbibliothèque disponible dans votre installation Python? Ce devrait être un module standard sous Linux et FreeBSD, si je lis correctement la documentation. import ossaudiodevtravaillé dans mon Ubuntu hors de la boîte.
lukeg

en fait, vous avez raison de dire que c'est présent, mais malheureusement, il n'était pas en mémoire, je ne peux donc pas le lire - il essaie de lire le module à partir du disque, ce qui entraîne une erreur d'E / S :-( C'était un excellent suggestion, cependant
Josh Hansen

@ user17219 c'est dommage. tldp.org/HOWTO/Alsa-sound-6.html - ce lien montre que vous pouvez essayer de modifier simplement catle fichier sur l’ /dev/snd/pcm...appareil.
lukeg

Je l'ai essayé mais j'ai des erreurs étranges. Je pense que les périphériques pcm ont changé depuis que ce document a été écrit car il existe maintenant des variantes * p et * c. J'ai essayé de les cataborder /dev/randomtous mais en vain.
Josh Hansen

0

Vous pouvez retirer le SSD de votre machine et dd/ photorec/ etc sur une autre machine en fonctionnement.

Voici le manuel de réparation Dell pour le 9550 - voir la section "Retrait du lecteur SSD". Une fois que vous avez retiré le lecteur, vous pouvez obtenir un boîtier externe M.2 USB et connecter le disque à un autre ordinateur pour récupérer vos données.


3
-1. Dans ce cas, vos conseils risquent d’aggraver les choses. Tout d'abord, nous ne pouvons pas savoir quels fichiers ont été validés sur le disque SSD, ils peuvent exister dans le cache. Certains fichiers n'ont même pas été enregistrés du tout: "Mais j'ai toujours des fichiers intéressants ouverts dans un éditeur. Je n'arrive pas à enregistrer les fichiers où que ce soit, mais je peux copier leur contenu." Ensuite, il y a un risque que le SSD ne s'initialise plus; cela fonctionne maintenant car il s'est initialisé avant le dysfonctionnement. Votre solution signifie à coup sûr perdre le contenu de la RAM et le contenu du disque SSD. J'ai bien peur que ce soit un coup de grâce .
Kamil Maciorowski

0

Un long plan, mais certaines distributions ont des protocoles intégrés de radioamateur. Un commun est fldigi. Essayez quel fldigi.

Si vous avez ceci ou un autre, ils convertissent le texte en audio, en utilisant généralement une variante de la manipulation par décalage de phase. La sortie est transmise à vos haut-parleurs / casque et est reçue par un ordinateur stable dans la même pièce, répertoriée dans son microphone. (Ceci élimine les radioamateurs qui transmettent et reçoivent le son sur les ondes).


0

L'approche des codes QR pourrait être bonne. Vous pouvez en afficher autant que vous le pouvez sur l'écran et les actualiser dès que votre terminal vous le permet. Enregistrez ensuite l’écran du XPS avec une caméra (et non avec une webcam) et essayez de déchiffrer la vidéo que vous avez sauvegardée. Il pourrait être utile d’utiliser la capture au ralenti même.


0

Excellent spectacle :) J'ai une suggestion: vous pouvez également lire les données à l'écran à l'aide d'une photodiode connectée à la carte son de l'autre ordinateur.


Ou connectez la sortie audio à l’entrée d’une carte son externe (pour éliminer le bruit). Inspirez
Milo O'h Le

La question elle-même et quelques réponses précédentes évoquent la capture des images à l’écran avec une caméra. Votre approche serait-elle meilleure? Ou même n'importe où près aussi bon? Si oui, s'il vous plaît expliquer. Ne répondez pas dans les commentaires; éditez votre réponse pour la rendre plus claire et plus complète.
G-Man dit 'Réintégrez Monica'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.