1+ import fs from 'fs' ;
2+ import path from 'path' ;
3+ import { spawn , spawnSync } from 'child_process' ;
4+ // import pLimit from 'p-limit';
5+ // import gunzip from "extract-zip";
6+ import gunzip from "gunzip-maybe"
7+ import { promisify } from "util" ;
8+
9+ const IMAGES_BASE = path . resolve ( 'images-base' ) ;
10+ const IMAGES_EXPANDED = path . resolve ( 'images-expanded' ) ;
11+ console . log ( `IMAGES_BASE: ${ IMAGES_BASE } ` ) ;
12+ console . log ( `IMAGES_EXPANDED: ${ IMAGES_EXPANDED } ` ) ;
13+
14+ // In bytes:
15+ const SECTOR_SIZE = 512
16+ const MBR_SIZE = 512
17+ const GPT_SIZE = SECTOR_SIZE * 34
18+ const MBR_BOOTSTRAP_CODE_SIZE = 446
19+
20+ /**
21+ * createDDArgs
22+ * dd helper
23+ * @param {string } partitionTableDiskImage
24+ * @param {string } nameImage
25+ * @param {int } resizeMultiplier
26+ * return {Array} argsList
27+ * // obs is the output block size and ibs is the input block size. If you specify bs without ibs or obs this is used for both.
28+ // Seek will just "inflate" the output file.
29+ // Seek=7 means that at the beginning of the output file,
30+ // 7 "empty" blocks with output block size=obs=4096bytes will be inserted.
31+ // This is a way to create very big files quickly.
32+ // Or to skip over data at the start which you do not want to alter.
33+ // Empty blocks only result if the output file initially did not have that much data.
34+ */
35+ function createDDArgs ( inImageName , outImageName , resizeMultiplier , partitionStartBytes ) {
36+ const partitionTableLabel = 'GPT' || 'DOS' ;
37+ const argsListMore = { }
38+ argsListMore . sizing = [ `count=${ MBR_BOOTSTRAP_CODE_SIZE } ` , 'seek=5' ] ;
39+ if ( partitionTableLabel === 'DOS' ) {
40+ argsListMore . sizing = [ `skip=${ MBR_SIZE } ` , `seek=${ MBR_SIZE } ` , `count=${ partitionStartBytes - MBR_SIZE } ` ] ;
41+ }
42+ if ( partitionTableLabel === 'GPT' ) {
43+ argsListMore . sizing = [ `skip=${ GPT_SIZE } ` , `seek=${ GPT_SIZE } ` , `count=${ partitionStartBytes - GPT_SIZE } ` ] ;
44+ }
45+ console . log ( partitionTableLabel , 'partitionTableLabel' , argsListMore . sizing , 'partitionStartBytes' , partitionStartBytes ) ;
46+
47+ const argsList = [
48+ `if=${ inImageName } ` ,
49+ `of=${ outImageName } ` ,
50+
51+ `ibs=${ 1024 * resizeMultiplier } ` ,
52+ // `bs=${resizeMultiplier}M`, // one MiB * resizeMultiplier
53+ `obs=1024` ,
54+ 'conv=notrunc' ,
55+ 'status=progress' ,
56+ // `iflag=count_bytes, skip_bytes`, // count and skip in bytes
57+ // `oflag=seek_bytes`// seek in bytes
58+ ...argsListMore . sizing
59+ ] ;
60+ return argsList ;
61+ }
62+
63+ // fork() exec() spawn() spawnSync()
64+ //https://github.com/adriano-di-giovanni/node-df/blob/master/lib/index.js
65+ const getPartitions = async ( image ) => {
66+ // const diskutilResults = await spawn('diskutil', ['list']);
67+ // console.log('diskutil', await diskutilResults);
68+ const partitions = spawn ( 'df' , [ '-hkP' ] , {
69+ // cwd: '/',
70+ // windowsHide: true,
71+ stdio : [
72+ /* Standard: stdin, stdout, stderr */
73+ // 'inherit',
74+ 'ignore' ,
75+ /* Custom: pipe:3, pipe:4, pipe:5 */
76+ 'pipe' , process . stderr
77+ ] } ) ;
78+ const partitionsResults = { partitions : [ ] , partitionsLength : 0 } ;
79+
80+ partitions . stdout . on ( 'data' , data => {
81+ const parsedDf = parseDf ( data ) ;
82+ // const strData = splitDf(data);
83+ // partitionsResults.partitionArrayLength = strData.length;
84+ // // console.log('strData.length', strData.length);
85+ // const columnHeaders = strData.shift();
86+ // console.log('columnHeaders', columnHeaders);
87+ // const formatted = formatDf(strData, columnHeaders);
88+ partitionsResults . partitions = parsedDf . partitions ;
89+ partitionsResults . partitionsLength = parsedDf . partitionsLength ;
90+ return partitionsResults ;
91+ } ) ;
92+
93+ // partitions.stderr.on('data', data => {
94+ // assert(false, 'NOPE stderr');
95+ // });
96+
97+ partitions . on ( 'close' , code => {
98+ console . log ( 'Child exited with' , code , 'and stdout has been saved' ) ;
99+ console . log ( 'partitionsResults' , partitionsResults ) ;
100+ return partitionsResults ;
101+ } ) ;
102+ return partitionsResults ;
103+ }
104+
105+ const parseDf = ( data ) => {
106+ const strData = splitDf ( data ) ;
107+ const columnHeaders = strData . shift ( ) ;
108+ const formatted = formatDf ( strData , columnHeaders ) ;
109+ return { partitions : formatted , partitionsLength : formatted . length } ;
110+ }
111+
112+ const splitDf = ( data ) => {
113+ return data . toString ( )
114+ . replace ( / + (? = ) / g, '' ) //replace multiple spaces between device parameters with one space
115+ . split ( '\n' ) //split by newline
116+ . map ( ( line ) => line . split ( ' ' ) ) ; //split each device by one space
117+ }
118+
119+ const formatDf = ( strData , columnHeaders ) => {
120+ return strData . map ( ( devDisk ) => {
121+ const partitionObj = { } ;
122+ for ( const [ index , value ] of devDisk . entries ( ) ) {
123+ partitionObj [ columnHeaders [ index ] ] = value
124+ }
125+ return partitionObj ;
126+ } ) ;
127+ }
128+
129+ export const expandImg = async ( img , partitionSizeStart = 1 ) => {
130+ if ( ! img ) {
131+ throw new Error ( `No img: "${ img } "` ) ;
132+ }
133+ const unzippedPath = `${ IMAGES_BASE } /unzipped/`
134+ if ( img . includes ( "zip" ) ) {
135+ // await gunzip(img, {dir: `${IMAGES_BASE}/unzipped/` });
136+ await gunzip ( `${ IMAGES_BASE } /zipped/${ img } ` , { dir : unzippedPath } ) ;
137+ }
138+ // else {
139+
140+ // const diskutilResults = await spawn('diskutil', ['list']);
141+ // console.log('diskutil', await diskutilResults);
142+ const generateRandomName = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
143+
144+ const inImageName = `${ unzippedPath } ${ img . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' ) } ` ;
145+ const outImageName = `${ IMAGES_EXPANDED } /${ generateRandomName } .img` ;
146+ const argsList = await createDDArgs ( inImageName , outImageName , 7 , partitionSizeStart ) ;
147+ await spawn ( 'dd' , argsList , {
148+ cwd : '/' ,
149+ windowsHide : true ,
150+ stdio : [
151+ /* Standard: stdin, stdout, stderr */
152+ 'ignore' ,
153+ /* Custom: pipe:3, pipe:4, pipe:5 */
154+ 'pipe' , process . stderr
155+ ] } ) ;
156+ return generateRandomName ;
157+ } ;
158+
159+ // strace dd if=/dev/disk5 of=./images-expanded/tuckers.img bs=4M conv=notrunc
160+ // dd if=/dev/disk5 of=./images-expanded/tuckers.img bs=4M conv="notrunc"
161+ // bs=4M
162+
163+ const getImages = async ( ) => {
164+ const image = 'balena-cloud-preloaded-raspberrypi4-64-2022.1.1-v12.11.0.img.zip'
165+ const { partitions, partitionsLength} = await getPartitions ( image ) ;
166+ console . log ( 'partitions' , await partitions , 'partitionsLength' , partitionsLength ) ;
167+ const imageName = await expandImg ( image )
168+ console . log ( 'imageName' , await imageName ) ;
169+ }
170+ getImages ( )
0 commit comments