From 0221229b80c286e95200ab6d7472fe2a1771540d Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 4 Jul 2013 21:33:59 +0200 Subject: [PATCH] inital commit --- build/linux_spiffs_test | Bin 0 -> 147788 bytes build/main.d | 36 + build/main.o | Bin 0 -> 3376 bytes build/spiffs_cache.d | 26 + build/spiffs_cache.o | Bin 0 -> 16272 bytes build/spiffs_hydrogen.d | 26 + build/spiffs_hydrogen.o | Bin 0 -> 23768 bytes build/spiffs_nucleus.d | 26 + build/spiffs_nucleus.o | Bin 0 -> 70272 bytes build/test_hydrogen.d | 37 + build/test_hydrogen.o | Bin 0 -> 99200 bytes build/test_spiffs.d | 37 + build/test_spiffs.o | Bin 0 -> 21560 bytes build/testrunner.d | 25 + build/testrunner.o | Bin 0 -> 10288 bytes build/testsuites.d | 1 + build/testsuites.o | Bin 0 -> 3128 bytes files.mk | 4 + makefile | 91 ++ src/default/spiffs_config.h | 94 ++ src/main.c | 31 + src/spiffs.h | 206 ++++ src/spiffs_cache.c | 289 +++++ src/spiffs_hydrogen.c | 484 ++++++++ src/spiffs_nucleus.c | 2063 +++++++++++++++++++++++++++++++++++ src/spiffs_nucleus.h | 584 ++++++++++ src/stypes.h | 28 + src/test_hydrogen.c | 1514 +++++++++++++++++++++++++ src/test_spiffs.c | 270 +++++ src/test_spiffs.h | 25 + src/testrunner.c | 120 ++ src/testrunner.h | 110 ++ src/testsuites.c | 13 + 33 files changed, 6140 insertions(+) create mode 100755 build/linux_spiffs_test create mode 100644 build/main.d create mode 100644 build/main.o create mode 100644 build/spiffs_cache.d create mode 100644 build/spiffs_cache.o create mode 100644 build/spiffs_hydrogen.d create mode 100644 build/spiffs_hydrogen.o create mode 100644 build/spiffs_nucleus.d create mode 100644 build/spiffs_nucleus.o create mode 100644 build/test_hydrogen.d create mode 100644 build/test_hydrogen.o create mode 100644 build/test_spiffs.d create mode 100644 build/test_spiffs.o create mode 100644 build/testrunner.d create mode 100644 build/testrunner.o create mode 100644 build/testsuites.d create mode 100644 build/testsuites.o create mode 100644 files.mk create mode 100644 makefile create mode 100644 src/default/spiffs_config.h create mode 100644 src/main.c create mode 100644 src/spiffs.h create mode 100644 src/spiffs_cache.c create mode 100644 src/spiffs_hydrogen.c create mode 100644 src/spiffs_nucleus.c create mode 100644 src/spiffs_nucleus.h create mode 100644 src/stypes.h create mode 100644 src/test_hydrogen.c create mode 100644 src/test_spiffs.c create mode 100644 src/test_spiffs.h create mode 100644 src/testrunner.c create mode 100644 src/testrunner.h create mode 100644 src/testsuites.c diff --git a/build/linux_spiffs_test b/build/linux_spiffs_test new file mode 100755 index 0000000000000000000000000000000000000000..335739f09e310a79dba9e3939f885ee7ad74b2c3 GIT binary patch literal 147788 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 literal 0 HcmV?d00001 diff --git a/build/main.d b/build/main.d new file mode 100644 index 0000000..6be1ada --- /dev/null +++ b/build/main.d @@ -0,0 +1,36 @@ + build/main.o build/main.d : src/main.c /usr/include/stdio.h /usr/include/features.h \ + /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/stypes.h src/spiffs.h \ + src/default/spiffs_config.h src/stypes.h src/spiffs_nucleus.h \ + src/testrunner.h /usr/include/x86_64-linux-gnu/sys/stat.h \ + /usr/include/x86_64-linux-gnu/bits/stat.h /usr/include/fcntl.h \ + /usr/include/x86_64-linux-gnu/bits/fcntl.h /usr/include/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ + /usr/include/x86_64-linux-gnu/bits/local_lim.h \ + /usr/include/linux/limits.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h diff --git a/build/main.o b/build/main.o new file mode 100644 index 0000000000000000000000000000000000000000..adba66d029a22f55220017783e205cd27df21627 GIT binary patch literal 3376 zcmbtW&5ImW5U=i@Y&OpBIJ;{wx`GohF+tOlkWV76vo0};O9+w01d&b8Z0}6#PWRH? zvxy?OCq)$WpeQ1E&_g_U@{*$<@#Mjy|A3IIAQ&(ZtKO^X^wc&8TF|eme)aWUz1Oe% z-E+$?xQ+ut4lKZZO;CW_-hH`a#18C*Sy*}TgWqqGewzj(z5WwQU*O{s1d}FRRZe53 z!JXV~m@{dZYqc7K6BgV>%&YB+;v55v<~r8Kn$1$P_2!*dDWHm+8RtM_uI@GF8#B(V z)5Ik16Hu=&AcQ4Y#JSXbcy7vToUK1oe{$-jIdblpikwvz<-#?)O2lhyL#!$CLj|_g z6nSrfT{Ej@)v%{=I(aTfLCaIHnn-ALkoYYb@*l40214D&Dz+M8+eM!O%zVH)+?*=Dr9o&iVq z20;qhAW8G_cncM|cNRE*VWqXyI^m!4k3&(}WHY`LNoxRGAnmnzMaEjV!1<-6v#mob z-BFy6+RITKq=`*> zi|9b0pS?BAgD&zswR|8_8g2xB9$wGEUkma8{BD+kZ_u@{JL<1S@p^(bBM04X8g7ZU zk@UtqgEWZy;dt~$6o=xECBg3{!(oW6@#7>9eeB_pEDw5ba-wjsx}FBZkS6{Asf8)% zxR;MVW$n;_q>rW?eQO1nM?qs``|$zmvClG?Lr!(%oe=yW=S8zkq9>eunn^|ukDoH9=o4NdTM`m+GcrM~bF5wD8>h2x+4m-tUPUb(?v9ItVy zEBJRDuifu3a7G-N%lZZW7KN(*?W^ct6MZFK`Z|M{e)lN%Q3ih?r9^uEr0cnp`yo0k zNZTAh_d0&Wcho1hs`@wh{d*YgWzzM_wz5oqHTdIR857y7Jh3#7s+ka8)nUTT-;Sg$ zxCiDBsnA96iq#8Zxo6(2af~$2ocJo7H&>ZFcu*l$^?-JMCwXQ^#&J3yv!(vX_k$`{ zX5lZs`3*=%@oFAsc?Q7SXU+!A>h3*D@|_~P$S49|Hztm3+XN!f`6MEYp6^LPl6eXK zxP{>n=K5UjSGdFH=h+Xn`*zk(-Uu8W| z_?N5?6n=wsx>t!L|4pmH7IVQP*56V1H>|(UT=(;1g-bs_FXbt}1+v#vxBS+~nbP@v Pm7i5;u|RiD`QG^lK3?Sb literal 0 HcmV?d00001 diff --git a/build/spiffs_cache.d b/build/spiffs_cache.d new file mode 100644 index 0000000..d3a682c --- /dev/null +++ b/build/spiffs_cache.d @@ -0,0 +1,26 @@ + build/spiffs_cache.o build/spiffs_cache.d : src/spiffs_cache.c src/spiffs.h \ + src/default/spiffs_config.h src/stypes.h /usr/include/stdio.h \ + /usr/include/features.h /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/spiffs_nucleus.h diff --git a/build/spiffs_cache.o b/build/spiffs_cache.o new file mode 100644 index 0000000000000000000000000000000000000000..1ac65f135fc7f345e2dd74211c3e579628d0b76c GIT binary patch literal 16272 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 literal 0 HcmV?d00001 diff --git a/build/spiffs_hydrogen.d b/build/spiffs_hydrogen.d new file mode 100644 index 0000000..816b12f --- /dev/null +++ b/build/spiffs_hydrogen.d @@ -0,0 +1,26 @@ + build/spiffs_hydrogen.o build/spiffs_hydrogen.d : src/spiffs_hydrogen.c src/spiffs.h \ + src/default/spiffs_config.h src/stypes.h /usr/include/stdio.h \ + /usr/include/features.h /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/spiffs_nucleus.h diff --git a/build/spiffs_hydrogen.o b/build/spiffs_hydrogen.o new file mode 100644 index 0000000000000000000000000000000000000000..5f56d68e09174c3c65badd7ffcbff9f345d0656f GIT binary patch literal 23768 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 v# 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 literal 0 HcmV?d00001 diff --git a/build/test_hydrogen.d b/build/test_hydrogen.d new file mode 100644 index 0000000..4bdcf9c --- /dev/null +++ b/build/test_hydrogen.d @@ -0,0 +1,37 @@ + build/test_hydrogen.o build/test_hydrogen.d : src/test_hydrogen.c src/testrunner.h src/test_spiffs.h \ + src/spiffs.h src/default/spiffs_config.h src/stypes.h \ + /usr/include/stdio.h /usr/include/features.h \ + /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/spiffs_nucleus.h \ + /usr/include/x86_64-linux-gnu/sys/stat.h \ + /usr/include/x86_64-linux-gnu/bits/stat.h /usr/include/fcntl.h \ + /usr/include/x86_64-linux-gnu/bits/fcntl.h /usr/include/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ + /usr/include/x86_64-linux-gnu/bits/local_lim.h \ + /usr/include/linux/limits.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h diff --git a/build/test_hydrogen.o b/build/test_hydrogen.o new file mode 100644 index 0000000000000000000000000000000000000000..508d9fd41c221d8167d4e1797dc8bddfd9bb06a6 GIT binary patch literal 99200 zcmeFa34B%6^#y!i9>m8WiGYfV8blNvm<3c+K%!9r5s4!{Uv(G1bN1|`3t9fNZ;RnpyabSJn2SrPY>V8|4ylji(-jJ*< z{2)Q_$%U_6^Hx#q(;Y9p%vGn_L#g)4F-7%_I&`GUMRkR{yEPQm7p`2*T19owfFFe$ zYo~7vrZru;hqmpvayb!Kr6`fj9$Q!V!r0pBF9g%}ar_D>OTiFz(RMfJtI#}xHw?OIgb*eUpMQF}*HdmpH) zxwo!(nQ67weW0i=Gt}f1VH=!IU13A*^oB49Kc(*F;#CKIOAsVzHNsc9bpvua%FwTM zg->rNbgpbDTuY+C_Sfly!ct#+Z>RP3h09J{pEqQEUExN8PA*(KWc;M0o`xdYE;Z{)Ps6`>T}lp&&2($Y1{Uv3|9A)XLvvBig&x^ z=(C#LbEH^-gF0)Pz&7U(AI?^9vj5UaI>d zb5(Hl;u@569v0QjE#Ug~?yEka#cponBpYEDPoB%6%bUM5hnhhqis~0TcJ2DsunX~A za>xb0ez6Pp{F-cs#L5P@=k~R-yrW5yT+BTs+ zmA!|on>(BoYMFO7 z$Kuc0jcv$b7yN85Xg_@;?Wb=HX38$AGkf3#^L|r&* zc-UTz%p)W7LL-~@360b{!J2(m294<8#m|kpxoP3L-_Cx{1%bO(8eI2R6Ou9HLE7ng z?6hR-`=zwf-0{v^{>1&@+>Q({c5uDRWb6><)z9}ui%d49^fy3oKGosklMUe|hku&Ki ziiy16^lP-Fe!izHb`(6kjB4z1@>?COZJ_z=4_?;gu;ZDtVshFoK2VwO{F%#W`f#P* z<WmzUqhN*Dc;nYrWr> zJ;q7h&&i&!Zth3CFK~Mo+QB?T79H@}-+1Tn(ze9Ly1CUHmA#AdexQJ9LEbOd*Ug>E zG12+Y9k!T}RlH01)+T)77G<`XF*N$H`eFyy&*eeimg64PJ0F zhsHH*-#4suWBpvt%0j5evGp!ce#Sa)9SH{xS4s~Y=6v^ovxp|$N~*DE`%Ao1y8cClK}q75X<`;nm(z(l?KI@ujs=FRK>FWQ^C!#64@qVkjHrsy+o z-shjXm7xv9-0v~!i?`YH?V{OF^I@%AXK*w)`gcQB_BJokDTg~qOrL)JUvW|0|AmDI zs2el?Hyr*yHXQ8b6`!Yza|Lv9Zks#QaD8r;1v*vvhIVJ)1XJ6)wPW>}w8A(Jt#0Wh z#;x*lA`xEpi@MCYH$(o~<D&0eft7Oim~X;`a=`F{LCWQ`qHe!EE#;HYGexE;M>?2U@1)448fG(>4nO2^uJ zxmq1HY}2-=@6%V`vnPvOf0r>eG18aqgJcY2oWH}Ez9L%9jOlGKrYoase1|bz9bMCm zX|%lRT+|3N7ncrV|seD0FG(2HzS(< zzcQxlBYoMvhQ{=FpBVF_MW#ed3|VZa$&|V|y5@J>E%5k_7^HidLO+VG@g1hn;^>-t znL@Wl*Z5vjXi2ojUZ&9P(KWu;6#8+r#$Kk-osl)Z#}v9dS|Dx;EsduC4^5%x=@{zy zK*#VeB7NEZ-1nM7zl;{S&QGD|ao=L5&?C_`Wv`S`^E>R79*b7n%anRNy2f{yQhdWF zV*2i7O7WpoM2+t?rPf4i>}5)=jjr*%rqsGuO=PNccW^U#s}_zB+ArN?bh_jz2KKaSMhz7N5% zb#_MAd6_yTw$7)~b(Tifkr00tS!c&2*M0QB2z{@}zOuLDPJ^b(Y-0s@xyTiR-9_^uYYB@<~b2|@VGv(>QC|6OMzh7j< z9aHxu7q$0vBHG$QrI0jV_3ka>MG|-Pbkih-i`Ze)4~V zD~g6r)}kY$Yu*r1^V{8-D&aXgQZ(bl9HZGWn!cB4`iD6#Z|GiE`1b0<=k7SKzVK~+ zp5v))jj4TZ?9hq4yXTP}NWKz1wNghWiPQD_-F)#C=w?f{xpyksD1THxxFIP_U!aU9_P`zx)zl9{!>` zrgXo-MS>nMqrIAYe#A|;x;)7LCko9FM%ruW0I7skfm zXdA7-#!p>yzsp4$H`^eP-*;W-o)pl=yXlcHOEb-JhX=3PaX;r`PRRTXXL-y2a~`8v37tv=KCwoJsg!-oLO zvu$aDc}i`It6B6}&6bE~+k7FF*=O6*wP*^^zCI|*`z1Y(`>cPyjfQ7NcQod~8ovw{ z@80|JH!C`L@#FZ~=nDUvbVbIp_kriY?M)>C~t zq1oG^*3YABMjmQ?t2=tAwIWh9_E77QXnNFCo&CWAUTEs3-(b#=GMBOtM~G$C^-bWbwLYWUbxzV||B(x-{IuloeUUf(gyvY4fiu{ge1)M-Oe-ELkSGoMIK zSNqJXuJ_N=)xFqhL*4GW!bLJB7nw1+p^yU-Y(J653#aOf7tkWIjGl(cxaA3D4Ythl z^(bgb?erz#+0QePU9z_PaRJXC{GrGpHEm7N$sC$Y=^xgEhTtntW%y9eJXnTi|J+6&4YYP|9G2?=h z3m4_hTSE`aeM_8qE<-H8cbwDbD;d9>q02>?5ql-N1ta*WMt=VD--*~(Y8S>`nK&PGnPcccCMA;DbhyH2%j z(K7A5%+~+%f%AT}yDPRmAX!s#cXUfPQEig>_aYm)h{;**7m@aMjG+0rl?*my&PS>AWpt&ZBl;p-_HNT< z@4KAJPOh7NXI;$_v->UDQ22n``xM@Z&wKI@$nMPi0U71sr*co%PJcT5Wy415a4wD? z>hipT{0@O{9#XsL_h@9p{vItud6Fk(s1`gr+qGa((-@1)xn|}R=FW$__~`&P;g+CnbdpW$ zmbk-K)HQU|2#<*tzQ7B^aVGoejtKVR<+y0ECPu7?ekxMv=Th2VYR3GbN%^sLPF>+g z|8eMK&pt8IsU27Tn{gA>%4pSg943qqU-*FKU5*sO)%QOgo3)W{Z0B1@*y;bRWAlb+ zANa|ge>28%b!J|)aG@Dab?d*&*t|JfY&We5CTA3+R6FY1#Kb9qyaV{xMB3JD!z#46rid7{C{PRe_TiJ?x-)^@?BT`pGQV@ zdw)N(w&>FOoo3cnzv^#|TJ_Dvh23}${gG%Np1_&4)vo&C4vAZ_}Ec%k7sXCo_!YGFJbL8TRi+?8#^^>isPD`^oPW zv1y0;C*a*@lN4NfYr?oArle#pETE%zxA=(Mr6p%Xbh ze`r7N{fX-9e|a(K1#?r<`P+&Uzz`!ZMoiUbNlmWY_smGcs7+i9RCy z*Jn`PkJdPmG-?a)?8G{BO<={UetVbsN3_f*9za8oa%V%-0q!qw6@!YImQOcOBoP zcCqg#LFUIvgVm4msfL`<3z`+|MhW_j(qoeeWQR5LAI^t;I9kl z6Yu?HJkyg;hbGzmx+DIkcFX7rQFqFs-`hpc^aMO_{mQtR&d3w|QCIk_kM`&#tIOna zD&G>=>@P35xVV$R`oT{#?3$jw@?@T@X&>E)^lj{o*Zf$m{ku=^bd0u0=%mo*L08IVq2ErCiJ7W6CC!G3skEKCZN&vSyN3B3SrTP&%q~d|AQx@@gq_W;#~YOe8~{ zc%5;Z@+Ov7Rgqm0C`j{^P*Yt#q5LW#j3vutm6W=0o5q$;DC8Wmi%( z3a%`#9$!#8cARpFFlzGTvPomgClOY~O(>XHF{XU%G|g3(aVVj+_P)<=&l=((yHuF0U%#t};bcdnwbR@=0ULrpDEaD%6VO zYvS#>vTEup9!;UAzU4KO%CD#?n^aa+MUA6KI`>PuVa7ED3cVWuIQJt-9e=Tel8H4Fs>?-y4N7FHCeqw+y|2yhmQ@l)TZ?QDSsW^e zV-qT9j8WK1N-HLfrT(2bYSOe4Z@XerSqT>hk(`NTW6EnLMwhwzLG}3Zs*)=!CRLY| zO{F166JU7Z`NIp&=zrEAngVWVsj4jPQe8&Fas0F~l@;U4CUq`#T_%IjrALQDc(fnC zJhZ#HqVDdqhx_aqbRi3VqA{bY{3=?dXwp(l{2cP%GrH zf&bVCjfu8{zZ(gzWy)uqH^nurIOn;(ZmOeg<9eJ{oOx=O)2j1*^;tu8_*?Z=hrjs` z@iCO=O*xuYA2`oix@q12H?@U*`#=AE3^*1#1~{$#_d{~8z5N08g7arvZbD!i03^q$Qi=$Obkl`m7I#s)d39Z!jz_CXUa z$mz#$PWzP4_5TaylI?HE@!0`8Ty}wEPxsj+k{!s#lI@q}1WjL~)Qj}jj6e1S%~Q^5 z^8_FJ2m7X4B)Fz`E}43h(!LFa?4U~~TeJHuoNqw zJuoqYE4EMhY#Yhu`|LrIZR4{COSZsg50Pv~pKUAIu0DIHWc&E+VUq3VvjvhZ^4WHh z9qO}3NcJM1Ju<V<&Y+oz+K8c^X{0)-tpWsWF$@U8*e|EwTuO*T{NAk-gKS=Tq zNPe*7S4e(Hg75by+pm=TQ0INJeS_qOCHPKMvi;MEU_aM?kGJHnHo0p#ByLFOKH;My zxph7`e)Q+AcVn=9J`ISASSU4z%(=16?Q&zAQ63)-eM-?*I=*D!yHdWc?&=)w$J#!p z9Yk_&*zI!$|32F%&rTZF1V@d*%o>pVPjL z&z1OGTb~={a|J#(*5{7&xpJTDDDGX(?zCEvnz#?G4kbJ~yb zx$Aw|c%Q5Dxd}dZqt8wDxp_WU?Q;uwr02As>T?UZjOUTh{>W$NNOqCW-jI+0-S#mD zsAKB(^C>{S#vPx!AV}`V-c%!Bb|>#~bOle{#g%f}Hwl{Z*eIop8-s_5_T4QBakHXh zYT2(t{pA_@Kk*uVnNQv0_1k%C_j=n$2Cec__rF5|sj*c->H$uL9r&Q{78yhj`EF@W zLy~*kkI|p8?beMFvks&s++D5qp9z=Fai2-gP0an5=U#vMw{zzmK^)S(>4(Z~V;+7#)+^TtE(F64D8kShho@f&ix3aCA_CCak+gOc4EDkNHp=_(3i92@Ex3B$U z-$;tam>_W{PkpNF26y5vwxRu#xcfy{)>@>N{+bH;qGFKPpZY6%JC^oj2Ub%lCvp~! zrzn0*tTv9ED2S{xjq8jK5(RFUFDyE60$rs@6gU?b4m_wlNVN0m5y8P;=V-T*W?K(} z*GcOTE=U~BJHO<@2WB2r6(lb+vs ze?`&r>b<=Bl9`hA8jCWs3&cWiU%FzZGv6mPKbgHb)fYZGv#oRcG@pGyvi*Ga=aRiZ z+HojzaXZMxY=CF4oO#gbnz2Emhy$8jxNK$+9OeTz7VBtQ$}ZPDeoMfYCwLx3ZEfkI z+JO|sC+Wv4}!y+^G-I_T&a1AQYn zZMQS%pEBu0tKGmvF%{)3yyzHaB+d>skClA7@n^Wk=PaB=XF0BLaJ>9xkss{jc`ft$ zV~qZoMAlGk=a#l}C6X&=wxcOJG$E^p>xJ{A7uM10;5``^@5v=Cd_%_>Pi7lWX2*F_ zp5e(4Gd!7)<;k3QPu> z8RN+_ah^Po;mLy;o;(zK;^&0s*M_~dDc&8PEP|Jo{^$XU}DL_I!qCzYjgr zar0KZCwv;mp1kEe2`9)lM}pC62F?}WGoDAj9YEu+-jb~%PJi|xv9Eu4lkInW*ic4d6!r|B-8S7#`1A-mJiLa ze3-S|CEjuc9j9|&bjh@QlCgYJwq-xWyM>nBFz6xKa2T8tZ~EX?-t;NXbb6uWe?Gu( zei#gkvwd2QJIHx{FmGaXiPe+h4VvFNlT`aB^XGUqH z@pNRIrx!Y7JThFAE|zQ272D?l{}^7^QjA zYK_u-$!nBulDtOgX33|cv^?I`55?8xnNfPoc=}kJr}sExISa@5vGh~P`bFnKJM~w^ zJ95r}K4z;VX1izj1C<0VTR8s`$(Ut$jWqj)nQp;b>ue_%-Xt^Z2^Z4j!Ud9FC-gZp z+tC60laiY?^Kdn%-|OgH)lcH}IqqT`FW*_LogwSRkmYM_cKMuzL$fQjp*DNc-$GJW z>pbB@CHU+|p%HS^A9^K|vAdFXZGzx9Zciea64Ak|wYw^F zQQk|P6E8JNq|Om3IW^#mv3~OnJTG|!XWLa>6(k0`0K$RK@Ob%l2YXM4i+quf-!vmX zEpEaUi!DFbFK|PJ4lvrYGl>h`K%!B9Jk73)9MgC4#ZqgeKf4?VbFKgP#`}LiU9#Zud$04K zuO9ghc-}PN`M3uBG@}9cWj5e`X+V(=`vdB>@16%GJHq$EFEU&4u(V>R_QH$tt+y4{4X8~M$T(k6|vRyM!O*z9bFOU_HO!DXa$?Kt4f642i*8s_<_g}-~T^&XT5j=Q?tE)0|t~Rb--N>!) zz7sEKbfz1hD`xn9xzbsL1%GBiu_#^AkZaHcW1EN!qe&xOSO@dmkd_lvQO z)BNhz)7_QkntomA*%(^)V518Qc=m0c=3CdbaX3Hdavh&ayL&tw0(Xz+w4fT~c z-@@m|X}-T+-s=-u4PfipPA~7B7+HQ4a}#|d%bqFV%X>Zqmo$z&^x_a);vNG?F4Pdb z7Yh0iyoVte(Ks^%e}@qGi>dC$W0Pb|}*(fS* z%L%UZPXtj;7be2^mql*lXL)$b38v(0+D$`;T}fjzbJH?63xuQT?m}gKP19b+ZLF{9 zi6NG)uVs^fqvDNWa;v3yP67-CcMHC-QK+4@>G4>+2>IgRCOnjehteoV>N^m`$et*>R< zfTQUhX)IsU|CH62#Ob8(po7^|1pi_Yx?aFo06|-^)YIEs_h-%gQowP#_~1&d5BHP z*Yp=5mhHcmeF+>*f0M@YHGMu^(bD*6dU%Ls>uXsta5Q~U8q3#oQ(iS`O)%huPjt-#Ur-_uyWrrXgGvg&L4h!D%x*RuA&(ezPi zEML>r?b7<1=7&AK&1`)wn*tn7PfKI@n!csIYE8-4^p8R;TVKl-0Y}rfrLlZX|0%?# z2_l zn*L>{wEvo35n|c;TJ{KVH2r8A%h&Yv$E5W&eM5+4>uXsZa5OzHjpb`Paa>wIOov#u zevXtSfurf%G?uUFbLF6l*EdZM4Y6!}Ejtf5njW6U@-=;*9FVcTrtc52Y<(?z063cd zSsKgN^cP*z@zL~`A(pMLWnTeD(|gibzNWA0lh)Vt)ghLxuVvQ&N7FOYSiYv$os`zs z^phc$t*>Pbz|r(?(^$TyTb`WO*L158%huPj*1*y9foUvX(~bJ3^)=l%#Ip6ZEEhPM zZj#3GHGT6bX?;yE2(fH^En5g2O)pAg`I_DpVpH-p{b`70>ucG+fTQWp(^$Ty7xYW} zujz#$maVU4w*W`ei_=)XrVlwit*_~}A(pMLWrqSs(*YuT;9 z(e&+UEML=ipOe-W$SC%QNYo3r!4}5W z`kI~;V%hpyRskGMSEjLiO?MoU*4K2W5X;urGWU!sRpn!NO&_1Gm#^vB=ce^FeQk(k z>uXspa5Q~g8q3#o{?N3(ruPf6Y<(?j2^>xDpT_bvy)?w8d1$`rb5_ zuj#hurTy3Rp&^#7f4r1+29Bn?rLlZXpBG|N@-;my#Ip6(_F=%$bh|W`uj%7LY)ZbS z)yJswJGFg&SntY|rY}fi`I;UTVpH-pJvzj){nxTm;Anbm8q3%8Uqft4zNX&{v26Y0 z+?eM+OZkqIPpy*yQl_S&`C%13t@S6bR-YM)*uy?P!$p?p%!)Z@oZs_D|RbFVJtlS(Jl zj4AU>$z(G}(_17mC|=x6#tV1KMmZZJeopie5R>^z>`s;ZtgxvYxP^h{1w z^%$y3=_K(4rfXc}X?X>qjfvsjwjsa1Cn#4TN;|PJ%5UEc@&aFv}6RgI4r|!=62`buoaq5y( z0o8gZtJ>1k&YGHwQ(bCPt(LxEolWng z+7T_0yC8S@npEqYE(cPp&Pk0+jZ+8NlVe*srtgrAxj#w0n{z}cl2hC&r+X`EfaolZ zsJ$t({?61C3f)n@2DL1wW2<*uL@B1B%EG1Z$PieNn>_RGWM1Fo0ezGE_ia!9t#t}v z@zT^LsaBI(nw&rLHJ7W-O@1Dv3OJpdGqXDR4Q1ZBIQ7CgOOH+Fot->5^;+^!`a6RD z+S6Yf`fEymhtXe4`fE*pE$EMN`+Xo`&j<8z0{u0CzmAmVzhmj|6#Cnj{+O)a$56Tx z{VhnOUQ2CB)=lkv&eCpcQ)|4#?lbkv%1gSf-lkY?YG*hkQ#(C_#>~!C7ndGONdUdj zw}YERo|ry#JPtQaNiAQ!O^d2YdDs7_>FsD5+aEUdf6Sj}ITYXS&;MM2oI`sZ6xZrV z@Es>Zibskrn%dRG^qo%3O*^TjPxvhTj-6Hl9)CSnzb6pY-)rjmIjM_X_oP;(o?km9 z^^yt5lHMV6@gTmOgk{n&M*|ZgBb-W2r;#&Rw=Ew6iSLs-{b- zi|%0Q@W*j-j%k@Yg;U9^#wMGrc35h4?v!0?*3yB`TA$zOzjz1Yk(|rz&})>smPn0q zQ!CP>%tIC~&Y_=4%aepld`bWEmPn%QeZ?z20WO}X;l*4~xrMTBy#KY)y!qcVu~=yPwax;r=Z zDILXV6L#jbZ=GqH0uVMTS(WlVRgjy^Pj%)N(vhn_y2>f{J(e+>{9?h~pTrouBUv@M zt45|Nx$;@+VmgfT1BYU)6PUw&j&vQT7p7iM(J?>w;A^?5f4bD!xed3Z*HxnU+6SynuUBRnt;y-pnkT9r+lNbV+i*aW);vy*z`CZ^$c~R9!jEy+o~m-?COfZ#eW;`Sm!F?_G26 z5Gx7a&Bli1?R;#yaPZLK7Y9{6x>FM?xe4L@X~m?Hs_Kf#()ck&h#Fz@$_2} zOZiohCH@tURAs`baaBS8p+gIY3@jYz^tg+hTxm@uopVp9nK%hO@o)X}FS9f+DAbqm zc}hvegfTqm=%tr|epi-_<e9eF;(C9`xx)tc9~6{Lt*#s;E|rvw9aTO-g5^jh z^e)9Q-6&jB`CWX}b{6f`lRDA&t?zU5rOI+{J43J48Z~Cg!_3G z^}0cN#UZa0Zm@I@_^pm*!C2=Jw1BVO=p^tjN(?gI&&fj}aBocYG13=rQcqU}gU=m! z)){B<2&|b@L+|hG9`O4XeP_CtE~ekeDJBP=H7xs$o>JzzkNcZrST$-2t#v%{&!<5) z$YH)yc$9Nb`sHwJFs$&xVP_2=UN}VFG#T|0%gpZ75$|4&SwbVdX0p@gc+q%C181By zsL;pU`Bo(sqF#m>{l?Mri_RT3+>c8cgC&zINQU~Ijr&zpMsk&3YiMB$1L&p=^$PWP zCH1q5Y*i4hq;CEGw%2buwW`3a{RO|!w*3Q6in0KRT>K842d!?*g-ZeL7{6#xi z4|suXQ4ud&g?SeaQ+j=pcB)_YM$rp+D`|=J z%U8zy)VJ8`TWwwRWcR_{9h6L>byVMmJF#qHCGCb|LgE%7o+Gl)p*a%H7@i!yt23L0 zFNbwYGA#(aGWYObJ^j}SvM!D*^^0|lTXS8kxK+Fp;5Vt#6!kBfb@8J`s-m)dob!Qq zfHjl-yoLRsn`jg+8isy9?AExFDb=G!)8T~dZQL=AQ>=CaET;dusfG)i=+QfWdFLcc za^%s4yw%msEn39<3s>FV%q?tHL2%~jr=L{Nv3NA?Dr&k6DxWl}vciwE z?%b7WdEzffTs166OwCUm(tMxP0=lr>mP+}Lx6b_0zeWCaIj8TNTTTSZa+y3$5S)hP z#kI*YqHy^L#%cSAKeT?%4ky6n*Dy|7Yragb<;lDpCUE(kTu9r&{GsK=Y5Qv#r_DX~ ztL4cH6I%bzzBq8tKZoT>S6)D6UrG79e2YrUbB5L?lM9=wMghfww|xFk&N*ox`bl|X zel%9glR+b`Kk{4Z&yw;>P(Q~vvMpiDr2ID-G5S>)qXsGGe-1ir|HZI;tCXKkcR14J zrzX`uz6-+J`5EOy!tw$sKP*H4pisY`l%K^S+Wwq@Eo-XGQyKNwhV{2fc}Yh7OTzl@rLI(WOGf>d!})_%4(3S^Hr5)%&o69Nz{hADB`9w6OkG)hBMK zpL1^5KK)I=CmH4c2+IrPk?;Bp|E~-E?D zx%(My7@z8Jd^SjVQ@U3dwlCSZvBqbslwX(O-|WyozQ@PgSsCpc7`CrK%GWSS?dLom z*6%0f7iZKT9@ZZzs!i~N%{1Q`cuRD4N@MrK5doq4p`rJk6+*Eh9+-O z^$Q3ukn$+|TrcQcHEkm80qMKC(tnTFRAt$OQJt&#h@i7OTj)HR-tgYJvchLv>6o(7 zHMA?9G`4~`EkR+RPb;t;eF&qChl8U8muW93)gKlnGZNMoKi5QA&ZzGR` zXjy?KA&^U)*nv=7Gv)6TYQbK{ww^n+)^ruYX35R5e02ppWDw} zVcUNtVOEo%;3cuY)fAd+aAQpUM;5UPy^W6^7s1WepG$>N{%;olwV&93enQCle=A|E zsqLhs+n)W2|DMuB0Qpk;`n{a+sQUeW?kgOdSv`!E)_+Lq8(MI=)VH|SQ~XJ(Z-!Yg zS?c#Q3yZd2>#wFnRQ$F{{FX=|$B&1i*4Mc1N0^=a1O+R8#h>)gsr#?+)B4YO>pPSC zxqlUmjfMb{?#pR);^YDnt6?akM|&^>15c8iumtjq$liO zRD>CCoGCy#jwf9W4{>)cMLGVZPXcsQv7_Lc*YECxvyT4!%(5cPI5s`{CAj{K#2xoj*-e7$&p*`e!-DJ2Hr#PO@m~~Ne{SK9 z?+JfbaQ&HtJFX}EOTqQ$2g3>DZC{#9+%Emuz$YFKju2cQ_jkwVRMtmueN0~uYz7Oi zkJr27apF%9Tpx?Cr#x@`&35&1c72V_3c>X;ba&iM{071G@$u`NN)YhB63ROI*ta{* zrm{VP>*Lz)_?qyxH2ImYk6F9pYQj$vTpy2i$J2zD3a*beD?UeXeH>YK-@(0t>(5Bs zc@ycZ6I>tv)p@i@aD8l7asF3Y*-w4kR`p4W)&a9fF-Ti7T6y(3%aulWq?k zX2V1>Xdb39k#u`RM0`3Kk6T?4L_!GtGNAle{hJSJler60lJO;ls1|J`TPa~Y$r9V4wP2--o zMKOGSc}P@z9*n^siNROK;7`Wj&&1#_$KZdA!T%bAzfCyDL4O{9un$APKO>K7@2(j9 z8`069!Rt@{`CsRV(rH6D+kKDf@uu%WI9l)z>6w zgHMjZXT;!h3Fr3ePt4tQNov=fG5nv#;E%@Oe7zwm&M(H`e~rQa8H0aHIQy?Zq3FP! z!<)O;pf_^ppCNO3y``Og)*yX@(%k^-+_P)IhsJx)m9t)^_x z9^q|1x#dCN3Ee_a|202GPt3TpNJy_fA>uC66V$s`cq@@|edtCb|BHh5U-+Y<6MJSC zjUCguXSc5QUyQ@MfLz|k+-?c)81dZ;B6K^!5LCe3Q6fSQYavt!8P`o4vn=%acLQp*I-ZO*ht#|K9oe=l$ z1d`GT-Q(|%QgN@I@x}h13M3NuvopbtQvO7ZX~c5^oqP7^rU6SAhasaD_vqf$1XKgg zh1Bx6drVYHBRKLtQsz@CM5OT>N%S|Ih|r7L6#H)xQhDzlkq?qk>$~;#H=~Kvhk9P_ z+j4Gi&T)A!x}irm=+7T|)){AaP?@L4ANm8v9abV0Oq@-#nHxG_`cA)~R=)Gaq={m50wFHls0*C`H; zdk9FEVY#19H^W^@E79{EjxND=w@INB_@6d~BFOqDg{YX^=AKb|>4%QOBV4|A#n%sX z<7*kb@!=K!ikkl2V7JjbKi$TB+O>ompJ(&N{J!+3d_C`CoUcVHzL?UyS-W?5*y;46 zwDNx|xaITtE^n;gpZ=7;ok7|x|DPUq`~j3!zW!S;taCcxC(?N&Z&v42!I?jh{!~ZL zR~at^d?x5{+*;8eZ$APZjN4Mc`Pg0cH-ZlK*YAPfANcSfIk@c4+A~~@YR5y3;6FqpX0CB z9p3;x#y?5tTfA{G#$jK;hfh?bH`w^)6c`46eyJrI44shI`_5>XF7ncjpexlvU!0!+Gn`8K| z#_&5dp~|#bKmEVj=*Wi9kK^Gsz`0*^zB~>%#^DvfF%G)~=P&Hv z&w-C|=+KOawAudZ4LJ7KWq@Pceh4_mZ5rShw|RhL+*S$B?sD8Nk$JQU_!zgXz{mE! z1N;G`qy2JZbIQ=h?Zr6XBe;#*1Hd0abyVjizy|^TA>i0AdHWKHHml!SaP|lLr61rp z--iK?{W1mc%ScQ8ycKZluX_N;{`wW**k8W`9Q&(r3vN|*yP68l?HWjb>d(Qz=l&fp z{d)}Xv4775KKAb*;NyB#4LHW_I>0e*4+4(s&nm#Ne_sI{`**wGwtxQ#{J{{1c6pSc zjpH+f{xm+v0*?K165!Y`g8|2W`JLd_pJ#!O{qhFzxnHJ9zq}88?3b?rN4uT!sWNTW zZfC){y+5EoZSRGEV}F$ZUIP4w0Y4Y;4S*K{{wCn(0sg+=>?ixA_uW4KIgGcaBKIshOc%v8eHwZ3^?2UyV%_VINQ+YNd6+Ywfi^2 zSG(^STi_XNNh(&ubW65QIQpLDy;ws)Yx)$X}~v)yiDcR1h->2o|6 z3vTWH!0@%br3P2KlK^MC{G1GLRe&?3&k0Qx+}fRP_-glBgR9;7fV16lv3m>P4C!-7 zw+U|T-f8%1ce%mU?#}^dyZZ0#+>J05U`^f|Ncf?K=24PWi{HMrV66L4+;0)<=Z08AX?OtH`YWGrutKG4Hv)!-6?&W|pq|eDs7TnscF?_XqmBH2ST)^3` zUT41%aJHe(;oU5_wY$jh)$Z*ESG)HD&UO!#>%0#F&NlQpzlR04c7JX7YImi<)$VTr zXS=<{?nc1b#=?H|LEG;Iw|4(v_-glcgR9-Y0nThfB8sKdAF0nfhaJCUX$0@kAJH+tS?)e5+yFUP&?XDENrGT>yeNJ?|;MVR$!&keN z23Nb&0cX3L#qPC$vkiR?^?Jdr-Fb$ub{864?cNDE+xc)I>!Q^{p9C7c{{=2lLQ|CI(U8KD#KTQ zW&)1>%mE$rXA$txpSumN{`?Gd(4QxPk9MC1{s3yH#^DdZN4tMExZ2$gI%sz%@X_vW z;GYb38}Sc9Xw!bw__r{)+C4;Y?nktHEb!58SKyDPI@;b-fsgGiGPv3;1|76p0(`VP z2KdZyX=AI~4fX z-Vwk@yJHQmcB?=K?cNA{w0kq~@%r#xz(>3H8(i%^3OZ z`*+YmyN%npFuUuYg_P8ObpN=X#Gm!Am38grwXp)S^1{}j_oP}9bAVm0zS5D zw86Dq6`+IdnhAWgI|uj!p}jwf;V%I`uG{wkAN^cmaP{*E&_O>p03ZE)9{AYrTY!&t z|7LKt`yuF{-95lZyN%=lKAzXOZnqR%`&aiDZ4It=JAn?`?E!qW+XwjA-hsf!_6{+) z+PxHX&~6Rz(e72i9|7ax2H>OJTMVvtmx2!3eGvF)_hH~;dshP=?LKXAwfhq2pxqt7 zN4p;ZAJ^?~fRA=l2UAdJ(|%XG`wPzF1nnLUe6)Kc@UgvJfsb}iGPv436Liq-4}gD? z^Vols0w34yD}ax7uQs^ay#aL4?#;kI3G6NcKDKum@X_u=23Nbk0UfmaN5BiouCCi( z8lCH9-3|`%&T)U?y4_N6?O){|2>40RuC}0q>-KTL$9DBHxVGzb(7|?HX!z>S2*A;w zQJ{nVTmgLa=W2tiKR195`m+T1X!kDQ<2v~>;G^Bg46b(9gAUq#4ftsHPrx4l{k|Re zXm_W<)vkXTiNEiOcAK{Koz3F`?dA#2CG!PV{r(CG_yrvM-AUJZO~@Abe(y9*7jcJBtAzF>DH@X_uQz#jqp=YWrPUp2Ve z{Tt}?1)UFok9I!>KDPI3;G^Ar4kc0Ablp(9tp(?Cf_6ItAMN%4KJJhD10U_4V{o;5 z5$K@Z(ZEN$A=VRQE?3a62P%tC7^@r ze+BTdUDFJ%?Ya(huw8c;zWTouaP;S1&_RD51wQ(-&fx0LbD)F%{0aDIcPsF5o%|>8 z(e7@8tKG(jb1!D^7xow2?iUUQKAul?is5$#{s6M0uh zLxGR&y~N;ZcO2-T-PyoLyVn69_g{;Ek9O}ixZ3?0=%C%lfRA=p10UP_4DiwJW`nEU zzkm+f-35HK`#JD&|FuuMz-_jF_cOTKJxp-hza4;&c8>u*wzn7XvAz8au67554z{-f z@Iq>dj@us_otxx*Y8l|@=TAYWKj=ILeDw24gR7s^9gM@Pz(>1p0Dl13-3ENL`-#ET?jF!V zyDi$2C~dai4-hKYJ|FNx zYMZVD%Rqi@H#gK^kx`1i>A^EL3%ZnGoF3EH&3)b9QU zSGz|D&i#mXk28FJ9}aKbfRA=hGkmpsmciBT1)zg=M;iVw8+p5Bz(>1PhOc&~8(i() z2s&tYk>Nkq$lJXg_-OZj!&keH7+meH10A&cis7$m1I2(IHz&yPxgk8zu5_!_q>4X$yU13Kvc zO@^;_e*`$jZ3*c70Q|oX_>Boye^wY={dod((4Q9!U;TL*aP(&j=-~ch8}J*0Kc5&} z{n-OL=uhrZRGBvJ7qr_60@xLGV7>7p;uKuh89rWh~;G^A_fj^r1K0~46b%} zgAUqF9ZjON*?!+waPIek^rv>)0-x<_+}Z(-{vQQ87`I-)NB{d7T>T#mI_Up!;G^A( zfsf<14ESib!r*H68qh(zcLUCL0uW^iq98^LXydm6s_c@FT= z&kF&^_?Hd!TxgK>KZ@Iq>z_U}5tv0wgV^tE690esZo2{^_%af}P68;6J= zB)G;|$KheX$9_4^@HGy-46boF9dyvovkhPEo(p`8|7C`+cE=f9?N);h+Wk4;80XiG zj>dUA@KNV8!`E@u;@EVYaa^?q9OHjP4Bj7bjQ_cUHzhN;u3l_#jl)>bLI1BaeD(h> z;G_Q!0*?Li8>6HB@|3~VpBF#}{rL)ToEQ1WrQ?bHb%4QjJ?~_2$p+mFUf^Ye-Ue5l z!7=zagR9O&gR4$$4E{(AzFu(cUmb_903YM_mf`C>dKdUOes&qY_SZKC*ZyjDd^-MU zx253fr`kOb_&82F7{1!=VsN$F7j)2WHQ+cNmKq(6+ath7ohJ=n$Ny`<$MOF@;26){ zMn~I~)7i&O1$6Ws2njx~JsX8`cgpCN!_oJSZP^=GWX)t@TR!T2l(9OvU>fMfjE z7+lBy3kH{A7QAL~okxE*xaxcogE#Mz9-r)w>a;Yt>U503FNnd%39kL9<7yi4F>cox zzRrt!;N$qY-SD-)?lHLb*9y=Xf?LG;794Egwe6{8nCjuYso(lZYWLuBB1_2-UrxzGp?UsTL+N}aU z+MNpgfnfJ~;Imzg+dRNgXCdfd+?E3${r{Q4)&G^CgZ@7We6;&C@J|N+Hv=E-zF}~+ z`yS|^-9x&OC~Z6**sh-6wKKTRUwW0T+mx^CNne8(cv>*P;Hq;`3_jK1sx!mjs&hvS z{(`|(=M{si&X4+Pah(4O_&DCy8eHve0v)uQ*Tct$s#W0UyW1PYhqj z!>Snmi@-lVDfESW$x?i|c@MeT@ zd-Zp44+4J>@Yex;3gFKIj_bot!R`9F8~E7oho4BIv|0X_~`$WfMeV~kJ0%C_&5%GOQ&*s$ASM7WAIwRZQO1EKDKKS;OBz= zvKV|7;5dGM4>%`}q(9!?7u=5XT?W^2p5T)x+U)w=4sg^zE(Slv-~~>Vp7}Aj`gwuD zl|RDZ%CC&U=NVjeZZWv(JP?EbS#TS-cMYy_+X*_jKD6QtZ5&UGTRVdnFp0LK4X%Ek zWbgvV4uXCLSN;Vt_+*2t&J=^I&b%1>@fdug!BzhSgRA~q23P)j23P*qG5CSJ2-2o; zE^s+|?~cJ$r$-DvPH@}rR~TITeFo^TYr4MO4LJ9EYw_oKqoezs*9@-ue+M1ZZ+xm3 z)cr*d-f7Twgy44lIa+Xz53ZBFfzJ}^Pk)1}KSMwV{V4(bOw!RfTmg6y;Ije8_4!u7 zx#_C&fZ(=WzW_e&kJbPm+x0txYrFmcI@qp%0zL%%`2uiUw{v-?OB=`eQuTMKT- z)nNu#e~twm^k)e0WeI0U_Dt;Ob8) z=%7E7fsg%I1N?zOdi=};d??_{1-I+KI>Bw6e+xRe4!jI}jPqX%uI+jsbg*4t0*>pz zzHE#(o)@?d911v&pYDL;_!%g;ZP!`A$MJI^@UdN^46f~(1UlHR8v)1hb1UFDetrTt zj-M5R+wrs7;Ofr?&_RFR2R@FU4}m{|Vy)}X*TBa(>@$FSC3_yV7M%MF?VbcUt^;QR zj{P_SaO}rPg4=$q0zUTRY~Z8+^#<3tEdd?${}I5kAD;jm`*9QC*pIIZZu{{qgR4LP z1ReC}OWp)XJiKUHkTnCO5+^z#B0-yEuIOH^g>v7RAgCEDnXuHJV z1r7~@kp@?tDKYp0gR9Q123MV*$KZd8!P{`KXtU$$P{BEFJQZ|a91naPSG^6c{WuVG zupf&7$8~QU;JEHx2{_J|d4S`5xm$4Cu6uxw^W_oXW4qQET-&t~bg*4-1CI0MBfxRK zd<8hpmllQGE9rGc_nU1DuKsioocjy?IUD#mU(N;o2&$v~dl~Q>6Rvj08C>mFgAUrg z6L4Jj9t0fwaUI~;kDCRz{rD>Iu^;~q{Knw_KMbz^e*rq^{{ehcpEiys_Tv$NV?TBV z9Q(1a;C9_R!{F-A`JjXTR0ALTaT@S>C}}^|0*>q6YQgQg*8qH6_ntBMksK7--ZZ$b zd+!;%z_Eki9|l*QCOr9R<2bWj#XA|ifXaC5VsPc36NAr;!5e9Hs$(AZh7%yB+XCz#j)4 z_VaxHpluECdC6CuO@^=I=VgOyyS9Q3w(C8^SGykoANRjs7{1zV#EUO&8fUe;pWrs0 zX93P})ANEkM&~k#!;gV~G4VAHj{uJGdD7^ppPLM>e!d1e7@toJU;SyqI|$L*a)XO_(OH-LX3wL|CoHp5qcJ~6oZvj=q0pJVwTo;DpP{c?T2TyTyL+P%{7 z)$VMAtKFMG2kovl{9qq%_g&zl-Cc&ScE2&W+HH28*R$jNXuvtC@$4=*$ARk%lYDQ3 zA1e6)fOA*rd)m(goOMP^evo`KHj4h#?g+q#1AYzYxS=5Nc>wqrpT`YfP)=m?V(^)wzue%z5dI2-?~?fZ!Qh<)|D(bC z3;v10kK%yP_O<_V{le`%MDUb+Yri}${^-AVulOX2G{4) zIvV_O@w2zVy9<7X!9Ns#iVfaX{262Le~8~z1|KZz#XNujI41R;;-!k~~lK-<2*|8%?pCEn~8N5XBDF)YnM`@11PZfV|G58S@AN_ag)z5#4 zKMx!J7FqY!8oZ^%?OB6w7rR>w{+8H%&)`=`J|*Sq&t}OVVDMXHe{rP24;G#71`oua zeg=P1bcPtbrRa<>c)sZT(BMskKh@yp3jaETFBJYFgWn|ipBP*oBMDX*yj1kp8T@ML z$L9_HLmAI+82nCIFSZ-}=hE+b{G$ERO86-$S6mKvgF_5{qS)iGq+PEY{CCo>4-Ec_jE6l2 z*W;wdGEUWQ3+bn37MJ{f2LFxdwlR3ABCtne>0`2Av6{na?=amM9_f4hu_83unwbmkd6ka2Q{!IRQ24;p-r__@~LpNidg z46fr!_hah+%`#8-82&ub$(3@>itBC!wJ^AzZyX>v>vxy2#$P=E=i~E)yM;A-gLducKu!2b6t$iO$NVA+Iy?fZ*c1L@8bc^{%bt{Xz)tmza69hfx)kk zalQ+5xLpO}&)0yX-R3f0w7t3x9AfZ$r2Yj4zgRl6%;5Km-D-pDzr%j5;5r@-7M&Xn zuE&3m8l8zUZdV(=#$mm|>m@$Vf)2-LmiW0DaE{LpWM01(qw`mT>v6!lpu_rlp7H_U ztbYgn@%CAa&K`s7@&7*DQMBp2=qGVI3~<(8BlEqJ;2LMu>1J^KckxaH9rk~j=nnv# z_3xK?ah}0nlW{WA;4h0#g~6LjoaY;Su;4#4_+rstZE&4On*`VP-YGgS8GK#6=0i9-v6-z9z?BRI=(+#Z%V6vgm|8eIGDvKXB@gX{c#0PvpP zW%v0A;Ovj`-vpfbdS1B=aOUg${mkI1lPednw7v6W9G(C;>yM;A-Yy0l{khTLwbCyO z1=sk$BmHu_!S&yNTn;)sp7r?oA;8(LUUz!L=;*vyWpK6o6zH)2NLgo|0i5;q`ql4^ zj{5nk!M|utm1z45=&*i+=x+nu`7d$a6{GW&!H3I*!$$i$GvRttAUX#F&ibom+_n>3 z<9~+e9Ao&8$h_zdIQRPkZ!S0!aMu4q_~#pa_487LH*4dCNbhuqxrCp6$_;Q_J+Aqxn z*M50gbXps}wyUk-A0_cT(%`CdsnK~^`fHrwtN#@SKjJX&_m!Z-?b7qun*hiDT5NRo z5uLjYU)yz`;eR1U)$mV|IP@{N>P$2`56k{&ronHM_SPF6ZP$+s{-gH3oj(R0Zf_v#^REEM z_O3EIQ$=UJ;cI)JHT(qIeyw-aE-J2f2hI#EdF!=9d6f1Y1hes zW4i_zofkys9K+Xkoewy#hYj?{TRGsY|D5zojnP*>ryG252PYHE1s(RkLHxfHaPi_-*A8?d!*P)=p?JAJ*#s=3C{h_?b<5sIvQ|n zS7)Phw&?UWd~MfhhJUBT;Y@?8&QPPH$E%YK{-Ae1xW?#cyXF{tMJHcj9_Vx@sGqF2 z_W+LVeaPr67M))kzP5Lb;ZKnG{MO*A^Ssf~^P}wspDgQSlUBYRI!`q|hZuaM9M2pr zxQ+*nLl=YJaE#aO4LaQkS|;%s3^>MTxY3y{I+q!~#;45i*GfDm8eDaz7#(fztp-0s z#_dmxj<)L|gXbLQ+xsif;r8lvxlMp$dp8@M_m1^Ce>8k;?^}j{kHlxY!ByuIqod;? zPtG&6-*r56F}TL3zrmYmKMJnnLE}(t@S(^1c3lQK9G{Ug4yypi_*`Xl&Jvwk!`JxK z8-5Gvmme8ib?!7e+TOJWuT6UYHyRyn*Jgv?-`VrGfDX5}LE8Hv;Mm@O8J&5ev&ZnY zz5B>{iN^nOiBF!vRi};M+V46ZPB6HRhhl?ke8w4E_g__lt3Mits}25TS0A5R(Bb$L zuw%5{4mieVnbFxLIu9DY#^({k|Fy((mBCeKz0uM3eq?aHkNcI;(RMXDz_&x=aJ9UD zq`BbSOm6QiX>WVLvAxF_o&Tq(rm3^K5s}`A7JXp1?1~ zcmR(J`ndv}^>`Nc?ZR(Iy<9Vo&RXI0vtRgoR#3d%EBb6dIpIfd4)$A}j#Wtw?P`bP z<~rN+9^t*Hm;J(dey9rPc;HXM+u{Ei;T#YA51jd|t_}WQf^i7)e#F%Q&U)E|>rR*0 zGY>t&m-Pnybxz0flfgLVkmHsgj;jod&LZgK#h%CAi107r=T6a~KYN5PvPFXNw%6&H zpC$Ntz;W;AyP|U*etszS^z&2U>v6nPM2G%-Equ%#a*DU(PN#7Sz&PWtj(b0U7oAb) zTo8NuxeyB(S?`~tT`d7;JE1?zg`b1|YNum<=HO?yJi@B7yMl3bS&N^#zTi3_wf#kP78GMV$XQDiT#_I zQ10WxsWUD*jCWdi8sh+`MTc?C2!G|aQ0}bLv3LuJw`FDcp84c?iSL(ZKA(q9r`R*z zG&t?ALVmi1Q)gIo81GKuyK%iZB>XS*lP5%<@lFaqupz`*aXOZV8uGyRxoaM#MW+az z-<*!M6Xt>Md1oFzLEiqJN9T&xx%p=v`h+tNgThxL4-W|Eb?`B8`t!p2P#)i_PCkIR z4vNk%IB$O}I?U&1!aMte{ufTi@|?lA_IHl^OaF z=dC!7+Q69y`rj#h`#{(~?Q|@z65`4_?&IQn(y2cPoguMjT=z-28RQ`^oH`?-!+sRs zhtBrQ{@A$Kv%ftldKi!WAWw???aCJcz1}-7U=8}d&WB| z_F3d-pK$8DBsy#l9|>o@SB0~kObee5;YNIqIrGCj%)p+!d1KgN*6CP&rjQ^19&?|c z*41G@>MVp#Cphbo`AIu&{i3Ap7;o1*Zu(p|!uOR^Cxd$5EalSwEy7Fi=MkrGaV3{p zsZsQd<36tEMCV25yejsL>n+DEu8oN69mjoK72!v4{8qu4Px^mSIG=~AIUS3ufVlo} z+{bl6be7z0S%{ZGBpV+Q^USy|bKK%8Ag-m3`?wwy&gU*31!r7ol>4M`KL1s4Iu=(A zaqV^7%H=wZF>vZLKd%XAxh1D#`kChVSG*leHa;%qf$KC5gHxY=P6+37XOm9H^h?k` z=D7LKdjA%j`qcSBIG@k@+3A>mbh-VCqM2mli7vFARU#lGJJ<87PpGIqe>)X2lN!L;!k z%_y?n(N${jRnSwOYyK(-p0;arE1 z70!8lKKDc&&W{(xp7YCP;hdkX2p_vL_*WOsdB6nPuYGMloc~G*=R8qXIOj+5!a0vn z6wdMUvT%+wSA=t%ur8eAV+kBL^q=EVDd8Od$qMIqOI|p~F^a-@zh4&4`{Igl-Y3?D z^S&m5aRvI%`vN{kL(cwiR_xht%nN6KsVJQNjk0iFuPefNJ*o@m^(cW87X9S)B_*8K Gh5rFw!RfjH literal 0 HcmV?d00001 diff --git a/build/test_spiffs.d b/build/test_spiffs.d new file mode 100644 index 0000000..d29fe3e --- /dev/null +++ b/build/test_spiffs.d @@ -0,0 +1,37 @@ + build/test_spiffs.o build/test_spiffs.d : src/test_spiffs.c /usr/include/stdio.h \ + /usr/include/features.h /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/stypes.h src/spiffs.h \ + src/default/spiffs_config.h src/stypes.h src/spiffs_nucleus.h \ + src/testrunner.h src/test_spiffs.h \ + /usr/include/x86_64-linux-gnu/sys/stat.h \ + /usr/include/x86_64-linux-gnu/bits/stat.h /usr/include/fcntl.h \ + /usr/include/x86_64-linux-gnu/bits/fcntl.h /usr/include/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/dirent.h \ + /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ + /usr/include/x86_64-linux-gnu/bits/local_lim.h \ + /usr/include/linux/limits.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h diff --git a/build/test_spiffs.o b/build/test_spiffs.o new file mode 100644 index 0000000000000000000000000000000000000000..af535e6b2482fa915fa92ed341493a5074bdce23 GIT binary patch literal 21560 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 literal 0 HcmV?d00001 diff --git a/build/testrunner.d b/build/testrunner.d new file mode 100644 index 0000000..5ef6745 --- /dev/null +++ b/build/testrunner.d @@ -0,0 +1,25 @@ + build/testrunner.o build/testrunner.d : src/testrunner.c /usr/include/stdio.h \ + /usr/include/features.h /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ + /usr/include/_G_config.h /usr/include/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \ + /usr/include/string.h /usr/include/xlocale.h src/testrunner.h diff --git a/build/testrunner.o b/build/testrunner.o new file mode 100644 index 0000000000000000000000000000000000000000..6d415a632a15c9ac5f81468e8f61d8c8813efd75 GIT binary patch literal 10288 zcmd5>dvH|M89(=K0+&3OtURpZtfUA|aW{;%BF|u0xj;~=M8#LK+1w;slHKh-csM>7 zR|-4PmW-p*hclr5g*9z$J5vijDAZD%j^m@Pw9{$P4%0AXG>lZ!s-)j{&iCz}oMiv$ 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 literal 0 HcmV?d00001 diff --git a/build/testsuites.d b/build/testsuites.d new file mode 100644 index 0000000..ae9197f --- /dev/null +++ b/build/testsuites.d @@ -0,0 +1 @@ + build/testsuites.o build/testsuites.d : src/testsuites.c src/testrunner.h diff --git a/build/testsuites.o b/build/testsuites.o new file mode 100644 index 0000000000000000000000000000000000000000..bcd50e08b56e5d69bee2e63e4638d9b158945cdc GIT binary patch literal 3128 zcmbtW&2JM&6o2bYf>S5PNm{iiJs7DiAjlfTM=Pj;+Z1REs`yYifRMAcC$>wx8||(M z^g>Pu+6#yN0aevY&%N~4O1C)caLvswn=Z%BCUV^I{oVj?({!qy1}7UH0U(q?ZM_|Omt~&?QH$a&BR}Y=y!X~1P-4$1 z$?Jr}q2jIdf-q4YZ|F*#_?^2Zkm|QKBY&t+^#7+ihd`(5tCI=WG`!ta(zRl`%#4je zw{7m9T!|&dkVo2C@DB{0c|9;fbXAX&gPjYv0Wtrp{~7j0T{cPn&{Ej8Cs=RS1eeEV z{a1|x)<4aUy?)$IJ0?T)8wREu=`kHpZ-vVMVoVy0;S z>IM5(V_Wf;eO+M8zI%xM%;f(CLWb=6A=|aX{CAUR*Erz4&Y$6l{OGBo{7rNJ>~ppA zvwI=IZi@bTRitzaI~H{mRRX+dhk@gbFi9@y|CtGeJ6?TW=PS*&p-_=m0;$Ye0tFPeJB~;a`3@baGPlkKjJ433(FWDkL5yHw)Yc~2Qf8?PH2=as$Z{{W*6%uE0P literal 0 HcmV?d00001 diff --git a/files.mk b/files.mk new file mode 100644 index 0000000..c7241c0 --- /dev/null +++ b/files.mk @@ -0,0 +1,4 @@ +FILES += spiffs_nucleus.c \ + spiffs_hydrogen.c \ + spiffs_cache.c + diff --git a/makefile b/makefile new file mode 100644 index 0000000..99c09a9 --- /dev/null +++ b/makefile @@ -0,0 +1,91 @@ +BINARY = linux_spiffs_test + +############ +# +# Paths +# +############ + +sourcedir = src +builddir = build + + +############# +# +# Build tools +# +############# + +CC = gcc $(COMPILEROPTIONS) +AS = gcc $(ASSEMBLEROPTIONS) +LD = ld +GDB = gdb +OBJCOPY = objcopy +OBJDUMP = objdump +MKDIR = mkdir -p + +############### +# +# Files and libs +# +############### + +FILES = main.c \ + test_spiffs.c \ + test_hydrogen.c \ + testsuites.c \ + testrunner.c +include files.mk +INCLUDE_DIRECTIVES = -I./${sourcedir} -I./${sourcedir}/default +COMPILEROPTIONS = $(INCLUDE_DIRECTIVES) + +############ +# +# Tasks +# +############ + +vpath %.c ${sourcedir} ${sourcedir}/default + +OBJFILES = $(FILES:%.c=${builddir}/%.o) + +DEPFILES = $(FILES:%.c=${builddir}/%.d) + +ALLOBJFILES += $(OBJFILES) + +DEPENDENCIES = $(DEPFILES) + +# link object files, create binary for flashing +$(BINARY): $(ALLOBJFILES) + @echo "... linking" + @${CC} $(LINKEROPTIONS) -o ${builddir}/$(BINARY) $(ALLOBJFILES) $(LIBS) + +-include $(DEPENDENCIES) + +# compile c files +$(OBJFILES) : ${builddir}/%.o:%.c + @echo "... compile $@" + @${CC} -g -c -o $@ $< + +# make dependencies +$(DEPFILES) : ${builddir}/%.d:%.c + @echo "... depend $@"; \ + rm -f $@; \ + ${CC} $(COMPILEROPTIONS) -M $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*, ${builddir}/\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ + +all: mkdirs $(BINARY) + +mkdirs: + -@${MKDIR} ${builddir} + +clean: + @echo ... removing build files in ${builddir} + @rm -f ${builddir}/*.o + @rm -f ${builddir}/*.d + @rm -f ${builddir}/*.out + @rm -f ${builddir}/*.hex + @rm -f ${builddir}/*.elf + @rm -f ${builddir}/*.map + @rm -f ${builddir}/*_disasm.s diff --git a/src/default/spiffs_config.h b/src/default/spiffs_config.h new file mode 100644 index 0000000..d25606f --- /dev/null +++ b/src/default/spiffs_config.h @@ -0,0 +1,94 @@ +/* + * spiffs_config.h + * + * Created on: Jul 3, 2013 + * Author: petera + */ + +#ifndef SPIFFS_CONFIG_H_ +#define SPIFFS_CONFIG_H_ + + +#include "stypes.h" +#include +#include +#include +#include + +// compile time switches + +#define SPIFFS_DBG(...) +#define SPIFFS_GC_DBG(...) +#define SPIFFS_CACHE_DBG(...) + +// define maximum number of gc runs to perform to reach desired free pages +#define SPIFFS_GC_MAX_RUNS 3 +// enable/disable statistics on gc +#define SPIFFS_GC_STATS 1 + +// enables/disable memory read caching of nucleus file system operations +// if enabled, memory area must be provided for cache in SPIFFS_init +#define SPIFFS_CACHE 1 +#if SPIFFS_CACHE +// enables memory write caching for file descriptors in hydrogen +#define SPIFFS_CACHE_WR 1 +// enable/disable statistics on caching +#define SPIFFS_CACHE_STATS 1 +#endif + +// checks header of each accessed page to validate state +#define SPIFFS_PAGE_CHECK 1 + +// Garbage collecting examines all pages in a block which and sums up +// to a block score. Deleted pages normally gives positive score and +// used pages normally gives a negative score (as these must be moved). +// The larger the score, the more likely it is that the block will +// picked for garbage collection. + +// garbage collecting heuristics - weight used for deleted pages +#define SPIFFS_GC_HEUR_W_DELET (10) +// garbage collecting heuristics - weight used for used pages +#define SPIFFS_GC_HEUR_W_USED (-1) + +// object name length +#define SPIFFS_OBJ_NAME_LEN (32 - sizeof(spiffs_obj_type)) + +// size of buffer on stack used when copying data +#define SPIFFS_COPY_BUFFER_STACK (64) + + +// types and constants + +// spiffs file descriptor index type +typedef s16_t spiffs_file; +// spiffs file descriptor attributes +typedef u32_t spiffs_attr; +// spiffs file descriptor mode +typedef u32_t spiffs_mode; + +// block index type +typedef u16_t spiffs_block_ix; // (address-phys_addr) / block_size +// page index type +typedef u16_t spiffs_page_ix; // (address-phys_addr) / page_size +// object id type - most significant bit is reserved for index flag +typedef u16_t spiffs_obj_id; +// object span index type +typedef u16_t spiffs_span_ix; +// object type +typedef u8_t spiffs_obj_type; + +// enable if only one spiffs instance with constant configuration will exist +// on the system +//#define SPIFFS_SINGLETON + +#ifdef SPIFFS_SINGLETON +// instead of giving parameters in config struct, singleton build must +// give parameters in defines below +#define SPIFFS_CFG_PHYS_SZ(ignore) (1024*1024*1) +#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (65536) +#define SPIFFS_CFG_PHYS_ADDR(ignore) (0) +#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (256) +#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (65536) +#endif + +#endif /* SPIFFS_CONFIG_H_ */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d4ff4e5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,31 @@ +/* + ============================================================================ + Name : spiffs_test_.c + Author : + Version : + Copyright : Your copyright notice + Description : Hello World in C, Ansi-style + ============================================================================ + */ + +#include +#include +#include + +#include "stypes.h" +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#include "testrunner.h" + +#include +#include +#include +#include +#include + +int main(void) { + run_tests(); + return EXIT_SUCCESS; +} + diff --git a/src/spiffs.h b/src/spiffs.h new file mode 100644 index 0000000..369f4b4 --- /dev/null +++ b/src/spiffs.h @@ -0,0 +1,206 @@ +/* + * spiffs.h + * + * Created on: May 26, 2013 + * Author: petera + */ + + + +#ifndef SPIFFS_H_ +#define SPIFFS_H_ + +#include "spiffs_config.h" + +#define SPIFFS_OK 0 +#define SPIFFS_ERR_FULL -10000 +#define SPIFFS_ERR_NOT_FOUND -10001 +#define SPIFFS_ERR_END_OF_OBJECT -10002 +#define SPIFFS_ERR_DELETED -10003 +#define SPIFFS_ERR_NOT_FINALIZED -10004 +#define SPIFFS_ERR_NOT_INDEX -10005 +#define SPIFFS_ERR_INDEX_SPAN_MISMATCH -10006 +#define SPIFFS_ERR_OUT_OF_FILE_DESCS -10007 +#define SPIFFS_ERR_FILE_CLOSED -10008 +#define SPIFFS_ERR_FILE_DELETED -10009 +#define SPIFFS_ERR_BAD_DESCRIPTOR -10010 +#define SPIFFS_ERR_IS_INDEX -10011 +#define SPIFFS_ERR_INDEX_WRONG_ID -10012 +#define SPIFFS_ERR_DATA_SPAN_MISMATCH -10013 +#define SPIFFS_ERR_DATA_WRONG_ID -10014 + +#define SPIFFS_COUNTINUE -11000 + +// spi read call type +typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); +// spi write call type +typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); +// spi erase call type +typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); + +// size of buffer on stack used when copying data +#define SPIFFS_COPY_BUFFER_STACK (64) + +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) \ + print(__VA_ARGS__) +#endif +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__) +#endif + +#define SPIFFS_APPEND (1<<0) +#define SPIFFS_TRUNC (1<<1) +#define SPIFFS_CREAT (1<<2) +#define SPIFFS_RDONLY (1<<3) +#define SPIFFS_WRONLY (1<<4) +#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) +#define SPIFFS_DIRECT (1<<5) + +#define SPIFFS_SEEK_SET (0) +#define SPIFFS_SEEK_CUR (1) +#define SPIFFS_SEEK_END (2) + +#define SPIFFS_TYPE_FILE (1) +#define SPIFFS_TYPE_DIR (2) +#define SPIFFS_TYPE_HARD_LINK (3) +#define SPIFFS_TYPE_SOFT_LINK (4) + +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) +#endif + +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) +#endif + +// phys structs + +// spiffs spi configuration struct +typedef struct { + // physical read function + spiffs_read hal_read_f; + // physical write function + spiffs_write hal_write_f; + // physical erase function + spiffs_erase hal_erase_f; +#ifndef SPIFFS_SINGLETON + // physical size of the spi flash + u32_t phys_size; + // physical offset in spi flash used for spiffs, + // must be on block boundary + u32_t phys_addr; + // physical size when erasing a block + u32_t phys_erase_block; + + // logical size of a block, must be on physical + // block size boundary and must never be less than + // a physical block + u32_t log_block_size; + // logical size of a page, must be at least + // log_block_size / 8 + u32_t log_page_size; +#endif +} spiffs_config; + +typedef struct { + // file system configuration + spiffs_config cfg; + // number of logical blocks + u32_t block_count; + + // cursor for free blocks, block index + spiffs_block_ix free_cursor_block_ix; + // cursor for free blocks, entry index + int free_cursor_obj_lu_entry; + // cursor when searching, block index + spiffs_block_ix cursor_block_ix; + // cursor when searching, entry index + int cursor_obj_lu_entry; + + // primary work buffer, size of a logical page + u8_t *lu_work; + // secondary work buffer, size of a logical page + u8_t *work; + // file descriptor memory area + u8_t *fd_space; + // available file descriptors + u32_t fd_count; + + s32_t errno; + + u32_t free_blocks; + + u32_t stats_p_allocated; + u32_t stats_p_deleted; +#if SPIFFS_GC_STATS + u32_t stats_gc_runs; +#endif + +#if SPIFFS_CACHE + void *cache; + u32_t cache_size; +#if SPIFFS_CACHE_STATS + u32_t cache_hits; + u32_t cache_misses; +#endif +#endif +} spiffs; + +typedef struct { + spiffs_obj_id obj_id; + u32_t size; + spiffs_obj_type type; + u8_t name[SPIFFS_OBJ_NAME_LEN]; +} spiffs_stat; + +struct spiffs_dirent { + spiffs_obj_id obj_id; + u8_t name[SPIFFS_OBJ_NAME_LEN]; + spiffs_obj_type type; + u32_t size; +}; + +typedef struct { + spiffs *fs; + spiffs_block_ix block; + int entry; +} spiffs_DIR; + +// functions + +/** + * Initializes the file system dynamic parameters + * @param fs the file system struct + * @param config the physical and logical configuration of the file system + * @param work a memory work buffer comprising 2*config->log_page_size + * bytes used throughout all file system operations + * @param fd_space memory for file descriptors + * @param fd_space_size memory size of file descriptors + * @param cache memory for cache, may be null + * @param cache_size memory size of cache + */ +s32_t SPIFFS_init(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size); + +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_attr attr); +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_attr attr, spiffs_mode mode); +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len); +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); +s32_t SPIFFS_remove(spiffs *fs, const char *path); +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh); +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s); +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh); +void SPIFFS_close(spiffs *fs, spiffs_file fh); +s32_t SPIFFS_errno(spiffs *fs); + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d); +s32_t SPIFFS_closedir(spiffs_DIR *d); +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e); + +#endif /* SPIFFS_H_ */ diff --git a/src/spiffs_cache.c b/src/spiffs_cache.c new file mode 100644 index 0000000..54c2fcf --- /dev/null +++ b/src/spiffs_cache.c @@ -0,0 +1,289 @@ +/* + * spiffs_cache.c + * + * Created on: Jun 23, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_CACHE + +static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache *cache = get_cache(fs); + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0; + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + cp->pix == pix ) { + SPIFFS_CACHE_DBG("CACHE_GET: have cache page %i for %04x\n", i, pix); + cp->last_access = cache->last_access; + return cp; + } + } + //SPIFFS_CACHE_DBG("CACHE_GET: no cache for %04x\n", pix); + return 0; +} + +static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = get_cache(fs); + spiffs_cache_page *cp = get_cache_page_hdr(fs, cache, ix); + if (cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { + u8_t *mem = get_cache_page(fs, cache, ix); + res = fs->cfg.hal_write_f(SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); + } + + cp->flags = 0; + cache->cpage_use_map &= ~(1 << ix); + + if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i objid %04x\n", ix, cp->obj_id); + } else { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i pix %04x\n", ix, cp->pix); + } + } + + return res; +} + +static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) { + // at least one free cpage + return SPIFFS_OK; + } + + // all busy, scan thru all to find the cpage which has oldest access + int i; + int cand_ix = -1; + u32_t oldest_val = 0; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = get_cache_page_hdr(fs, cache, i); + if ((cache->last_access - cp->last_access) > oldest_val && + (cp->flags & flag_mask) == flags) { + oldest_val = cache->last_access - cp->last_access; + cand_ix = i; + } + } + + if (cand_ix >= 0) { + res = spiffs_cache_page_free(fs, cand_ix, 1); + } + + return res; +} + +static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) { + spiffs_cache *cache = get_cache(fs); + if (cache->cpage_use_map == 0xffffffff) { + // out of cache memory + return 0; + } + int i; + for (i = 0; i < cache->cpage_count; i++) { + if ((cache->cpage_use_map & (1<cpage_use_map |= (1<last_access = cache->last_access; + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page %i\n", i); + return cp; + } + } + // out of cache entries + return 0; +} + +// ------------------------------ + +void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + if (cp) { + spiffs_cache_page_free(fs, cp->ix, 0); + } +} + +s32_t spiffs_phys_rd( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *dst) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); + cache->last_access++; + if (cp) { +#if SPIFFS_CACHE_STATS + fs->cache_hits++; +#endif + cp->last_access = cache->last_access; + } else { + if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { + // for second layer lookup functions, we do not cache in order to prevent shredding + return fs->cfg.hal_read_f( + addr , + len, + dst); + } +#if SPIFFS_CACHE_STATS + fs->cache_misses++; +#endif + res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + cp = spiffs_cache_page_allocate(fs); + if (cp) { + cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; + cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + } + + s32_t res2 = fs->cfg.hal_read_f( + addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + get_cache_page(fs, cache, cp->ix)); + if (res2 != SPIFFS_OK) { + res = res2; + } + } + u8_t *mem = get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + return res; +} + +s32_t spiffs_phys_wr( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *src) { + spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + spiffs_cache *cache = get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + + if (cp) { + // have a cache page + // copy in data to cache page + + if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE && + (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { + // page is being deleted, wipe from cache - unless it is a lookup page + spiffs_cache_page_free(fs, cp->ix, 0); + return fs->cfg.hal_write_f(addr, len, src); + } + + u8_t *mem = get_cache_page(fs, cache, cp->ix); + memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); + + cache->last_access++; + cp->last_access = cache->last_access; + + if (cp->flags && SPIFFS_CACHE_FLAG_WRTHRU) { + // page is being updated, no write-cache, just pass thru + return fs->cfg.hal_write_f(addr, len, src); + } else { + return SPIFFS_OK; + } + } else { + // no cache page, no write cache - just write thru + return fs->cfg.hal_write_f(addr, len, src); + } +} + +#if SPIFFS_CACHE_WR +spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) { + spiffs_cache *cache = get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { + // all cpages free, no cpage cannot be assigned to obj_id + return 0; + } + + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) && + cp->obj_id == fd->obj_id) { + return cp; + } + } + + return 0; +} + +spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) { + // before this function is called, it is ensured that there is no already existing + // cache page with same object id + spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + spiffs_cache_page *cp = spiffs_cache_page_allocate(fs); + if (cp == 0) { + // could not get cache page + return 0; + } + + cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; + cp->obj_id = fd->obj_id; + fd->cache_page = cp; + return cp; +} + +void spiffs_cache_fh_release(spiffs *fs, spiffs_cache_page *cp) { + if (cp == 0) return; + int i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0 && cur_fd->cache_page == cp) { + cur_fd->cache_page = 0; + } + } + spiffs_cache_page_free(fs, cp->ix, 0); + + cp->obj_id = 0; +} + +#endif + +void spiffs_cache_init(spiffs *fs) { + if (fs->cache == 0) return; + u32_t sz = fs->cache_size; + u32_t cache_mask = 0; + int i; + int cache_entries = + (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs)); + if (cache_entries <= 0) return; + + for (i = 0; i < cache_entries; i++) { + cache_mask <<= 1; + cache_mask |= 1; + } + + spiffs_cache cache; + memset(&cache, 0, sizeof(spiffs_cache)); + cache.cpage_count = cache_entries; + cache.cpages = (u8_t *)(fs->cache + sizeof(spiffs_cache)); + + cache.cpage_use_map = 0xffffffff; + cache.cpage_use_mask = cache_mask; + memcpy(fs->cache, &cache, sizeof(spiffs_cache)); + + spiffs_cache *c = get_cache(fs); + + memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs)); + + c->cpage_use_map &= ~(c->cpage_use_mask); + for (i = 0; i < cache.cpage_count; i++) { + get_cache_page_hdr(fs, c, i)->ix = i; + } +} + +#endif // SPIFFS_CACHE diff --git a/src/spiffs_hydrogen.c b/src/spiffs_hydrogen.c new file mode 100644 index 0000000..23d8f7b --- /dev/null +++ b/src/spiffs_hydrogen.c @@ -0,0 +1,484 @@ +/* + * spiffs_hydrogen.c + * + * Created on: Jun 16, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh); + +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_attr attr) { + SPIFFS_LOCK(fs); + spiffs_obj_id obj_id; + s32_t res; + + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (u8_t*)path, SPIFFS_TYPE_FILE, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +} + +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_attr attr, spiffs_mode mode) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + + s32_t res = spiffs_fd_find_new(fs, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix); + if ((mode & SPIFFS_CREAT) == 0) { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((mode & SPIFFS_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { + spiffs_obj_id obj_id; + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (u8_t*)path, SPIFFS_TYPE_FILE, &pix); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + mode &= ~SPIFFS_TRUNC; + } else { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + res = spiffs_object_open_by_page(fs, pix, fd, attr, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + if (mode & SPIFFS_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return fd->file_nbr; +} + +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + if (fd->fdoffset + len >= fd->size) { + // reading beyond file size + s32_t avail = fd->size - fd->fdoffset; + if (avail <= 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT); + } + res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + fd->fdoffset += avail; + SPIFFS_UNLOCK(fs); + return avail; + } else { + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + } else { + // reading within file size + res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return len; +} + +static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) { + s32_t res = SPIFFS_OK; + s32_t remaining = len; + if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) { + s32_t m_len = MIN(fd->size - offset, len); + res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len); + SPIFFS_CHECK_RES(res); + remaining -= m_len; + buf += m_len; + offset += m_len; + } + if (remaining > 0) { + res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining); + SPIFFS_CHECK_RES(res); + } + return len; + +} + +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + u32_t offset; + + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + offset = fd->fdoffset; + +#if SPIFFS_CACHE_WR + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } +#endif + if (fd->mode & SPIFFS_APPEND) { + if (fd->size == SPIFFS_UNDEFINED_LEN) { + offset = 0; + } else { + offset = fd->size; + } +#if SPIFFS_CACHE_WR + if (fd->cache_page) { + offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size); + } +#endif + } + + SPIFFS_DBG("SPIFFS_write %i %04x offs:%i len %i\n", fh, fd->obj_id, offset, len); + +#if SPIFFS_CACHE_WR + if ((fd->mode & SPIFFS_DIRECT) == 0) { + if (len < SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + // small write, try to cache it + u8_t alloc_cpage = 1; + if (fd->cache_page) { + // have a cached page for this fd already, check cache page boundaries + if (offset < fd->cache_page->offset || // writing before cache + offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache + offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page + { + // boundary violation, write back cache first and allocate new + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:&04x, boundary viol, offs:%i size:%i\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + get_cache_page(fs, get_cache(fs), fd->cache_page->ix), fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fh_release(fs, fd->cache_page); + } else { + // writing within cache + alloc_cpage = 0; + } + } + + if (alloc_cpage) { + fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd); + if (fd->cache_page) { + fd->cache_page->offset = offset; + fd->cache_page->size = 0; + SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page %i for fd %i:%04x\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id); + } + } + + if (fd->cache_page) { + u32_t offset_in_cpage = offset - fd->cache_page->offset; + SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page %i for fd %i:%04x, offs %i:%i len %i\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, + offset, offset_in_cpage, len); + spiffs_cache *cache = get_cache(fs); + u8_t *cpage_data = get_cache_page(fs, cache, fd->cache_page->ix); + memcpy(&cpage_data[offset_in_cpage], buf, len); + fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return len; + } else { + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES(fs, res); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return res; + } + } else { + // big write, no need to cache it - but first check if there is a cached write already + if (fd->cache_page) { + // write back cache first + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:%04x, big write, offs:%i size:%i\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + get_cache_page(fs, get_cache(fs), fd->cache_page->ix), fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fh_release(fs, fd->cache_page); + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES(fs, res); + } + } + } +#endif + + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES(fs, res); + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + switch (whence) { + case SPIFFS_SEEK_CUR: + offs = fd->fdoffset+offs; + break; + case SPIFFS_SEEK_END: + offs = (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size) + offs; + break; + } + + if (offs > fd->size) { + res = SPIFFS_ERR_END_OF_OBJECT; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (fd->cursor_objix_spix != objix_spix) { + spiffs_page_ix pix; + res = spiffs_obj_lu_find_id_and_index( + fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->cursor_objix_spix = objix_spix; + fd->cursor_objix_pix = pix; + } + fd->fdoffset = offs; + + SPIFFS_UNLOCK(fs); + + return 0; +} + +s32_t SPIFFS_remove(spiffs *fs, const char *path) { + SPIFFS_LOCK(fs); + + spiffs_fd fd; + spiffs_page_ix pix; + s32_t res; + fd.file_nbr = 0; + fd.fs = fs; + res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_open_by_page(fs, pix, &fd, 0,0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_truncate(&fd, 0, 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return 0; +} + +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_cache_fh_release(fs, fd->cache_page); +#endif + + res = spiffs_object_truncate(fd, 0, 1); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return 0; +} + +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + res = spiffs_phys_rd(fs, +#if SPIFFS_CACHE + SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh, +#endif + SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + s->obj_id = objix_hdr.p_hdr.obj_id; + s->type = objix_hdr.type; + s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN); + + SPIFFS_UNLOCK(fs); + + return res; +} + +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { + s32_t res = SPIFFS_OK; +#if SPIFFS_CACHE_WR + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES(fs, res); + + if ((fd->mode & SPIFFS_DIRECT) == 0) { + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } + if (fd->cache_page) { + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:%04x, flush, offs:%i size:%i\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + get_cache_page(fs, get_cache(fs), fd->cache_page->ix), fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fh_release(fs, fd->cache_page); + } + } +#endif + + return res; +} + +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { + s32_t res = SPIFFS_OK; +#if SPIFFS_CACHE_WR + SPIFFS_LOCK(fs); + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs,res); + SPIFFS_UNLOCK(fs); +#endif + + return res; +} + +void SPIFFS_close(spiffs *fs, spiffs_file fh) { + SPIFFS_LOCK(fs); + +#if SPIFFS_CACHE + spiffs_fflush_cache(fs, fh); +#endif + spiffs_fd_return(fs, fh); + + SPIFFS_UNLOCK(fs); +} + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) { + d->fs = fs; + d->block = 0; + d->entry = 0; + return d; +} + +static s32_t spiffs_read_dir_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + u32_t user_data, + void *user_p) { + s32_t res; + spiffs_page_object_ix_header objix_hdr; + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_ERASED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_COUNTINUE; + } + + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + if (res != SPIFFS_OK) return res; + if ((objix_hdr.p_hdr.obj_id & SPIFFS_OBJ_ID_IX_FLAG) && + objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags& (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL)) == SPIFFS_PH_FLAG_DELET) { + struct spiffs_dirent *e = (struct spiffs_dirent *)user_p; + e->obj_id = objix_hdr.p_hdr.obj_id; + strcpy((char *)e->name, (char *)objix_hdr.name); + e->type = objix_hdr.type; + e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + return SPIFFS_OK; + } + + return SPIFFS_COUNTINUE; +} + +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { + SPIFFS_LOCK(fs); + + spiffs_block_ix bix; + int entry; + s32_t res; + struct spiffs_dirent *ret = 0; + + res = spiffs_obj_lu_find_entry_visitor(d->fs, + d->block, + d->entry, + SPIFFS_VIS_NO_WRAP, + 0, + spiffs_read_dir_v, + 0, + e, + &bix, + &entry); + if (res == SPIFFS_OK) { + d->block = bix; + d->entry = entry + 1; + ret = e; + } else { + d->fs->errno = res; + } + SPIFFS_UNLOCK(fs); + return ret; +} + +s32_t SPIFFS_closedir(spiffs_DIR *d) { + return SPIFFS_OK; +} + diff --git a/src/spiffs_nucleus.c b/src/spiffs_nucleus.c new file mode 100644 index 0000000..7a5496a --- /dev/null +++ b/src/spiffs_nucleus.c @@ -0,0 +1,2063 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH;\ + if (((ph).obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != ((objid) & ~SPIFFS_OBJ_ID_IX_FLAG)) return SPIFFS_ERR_INDEX_WRONG_ID; + +#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; \ + if (((ph).obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != ((objid) & ~SPIFFS_OBJ_ID_IX_FLAG)) return SPIFFS_ERR_DATA_WRONG_ID; + +static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_DATA(ph, fd->obj_id, spix); +#endif + return res; +} + +static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix); +#endif + return res; +} + +s32_t SPIFFS_init(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size) { + SPIFFS_LOCK(fs); + memset(fs, 0, sizeof(spiffs)); + memcpy(&fs->cfg, config, sizeof(spiffs_config)); + fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); + fs->work = &work[0]; + fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; + memset(fd_space, 0, fd_space_size); + // align fd_space pointer to pointer size byte boundary, below is safe + u8_t ptr_size = sizeof(void*); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-to-int-cast" + u8_t addr_lsb = ((u8_t)fd_space) & (ptr_size-1); +#pragma GCC diagnostic pop + if (addr_lsb) { + fd_space += (ptr_size-addr_lsb); + fd_space_size -= (ptr_size-addr_lsb); + } + fs->fd_space = fd_space; + fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); + + // align cache pointer to 4 byte boundary, below is safe +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-to-int-cast" + addr_lsb = ((u8_t)cache) & (ptr_size-1); +#pragma GCC diagnostic pop + if (addr_lsb) { + cache += (ptr_size-addr_lsb); + cache_size -= (ptr_size-addr_lsb); + } + if (cache_size & (ptr_size-1)) { + cache_size -= (cache_size & (ptr_size-1)); + } +#if SPIFFS_CACHE + fs->cache = cache; + fs->cache_size = cache_size; + spiffs_cache_init(fs); +#endif + + s32_t res = spiffs_obj_lu_scan(fs); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_DBG("page index byte len: %i\n", SPIFFS_CFG_LOG_PAGE_SZ(fs)); + SPIFFS_DBG("object lookup pages: %i\n", SPIFFS_OBJ_LOOKUP_PAGES(fs)); + SPIFFS_DBG("page pages per block: %i\n", SPIFFS_PAGES_PER_BLOCK(fs)); + SPIFFS_DBG("page header length: %i\n", sizeof(spiffs_page_header)); + SPIFFS_DBG("object header index entries: %i\n", SPIFFS_OBJ_HDR_IX_LEN(fs)); + SPIFFS_DBG("object index entries: %i\n", SPIFFS_OBJ_IX_LEN(fs)); + SPIFFS_DBG("available file descriptors: %i\n", fs->fd_count); + SPIFFS_DBG("free blocks: %i\n", fs->free_blocks); + + SPIFFS_UNLOCK(fs); + + return 0; +} + +s32_t SPIFFS_errno(spiffs *fs) { + return fs->errno; +} + +#if !SPIFFS_CACHE + +s32_t spiffs_phys_rd( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *dst) { + return fs->cfg.hal_read_f(addr, len, dst); +} + +s32_t spiffs_phys_wr( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *src) { + return fs->cfg.hal_write_f(addr, len, src); +} + +#endif + +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len) { + s32_t res; + u8_t b[SPIFFS_COPY_BUFFER_STACK]; + while (len > 0) { + u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, fh, dst, chunk_size, b); + SPIFFS_CHECK_RES(res); + len -= chunk_size; + src += chunk_size; + dst += chunk_size; + } + return SPIFFS_OK; +} + +// Find object lookup entry containing given id with visitor. +// Iterate over object lookup pages in each block until a given object id entry is found. +// When found, the visitor function is called with block index, entry index and user_data. +// If visitor returns SPIFFS_CONTINUE, the search goes on. Otherwise, the search will be +// ended and visitor's return code is returned to caller. +// If no visitor is given (0) the search returns on first entry with matching object id, or +// when whole area is searched without finding object id. +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + u32_t user_data, + void *user_p, + spiffs_block_ix *block_ix, + int *index_entry) { + s32_t res = SPIFFS_OK; + s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs); + spiffs_block_ix cur_block = starting_block; + u32_t cur_block_addr = SPIFFS_BLOCK_TO_PADDR(fs, starting_block); + + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = starting_index_entry; + u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // wrap initial + if (cur_entry >= SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { + cur_entry = 0; + cur_block++; + cur_block_addr = SPIFFS_BLOCK_TO_PADDR(fs, cur_block); + if (cur_block >= fs->block_count) { + // block wrap + cur_block = 0; + cur_block_addr = SPIFFS_CFG_PHYS_ADDR(fs); + } + } + + // check each block + while (res == SPIFFS_OK && entry_count > 0) { + int obj_lookup_page = cur_entry / entries_per_page; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages + cur_entry < SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page + { + if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) { + if (block_ix) *block_ix = cur_block; + if (index_entry) *index_entry = cur_entry; + if (v) { + res = v(fs, obj_lu_buf[cur_entry-entry_offset], cur_block, cur_entry, user_data, user_p); + if (res == SPIFFS_COUNTINUE) { + res = SPIFFS_OK; + cur_entry++; + entry_count--; + continue; + } else { + return res; + } + } + return SPIFFS_OK; + } + entry_count--; + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_ERR_NOT_FOUND; + } else { + // block wrap + cur_block = 0; + cur_block_addr = SPIFFS_CFG_PHYS_ADDR(fs); + } + } + } // per block + + SPIFFS_CHECK_RES(res); + + return SPIFFS_ERR_NOT_FOUND; +} + + +static s32_t spiffs_obj_lu_scan_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + u32_t user_data, + void *user_p) { + if (obj_id == SPIFFS_OBJ_ID_FREE) { + if (ix_entry == 0) { + fs->free_blocks++; + // todo optimize further, return SPIFFS_NEXT_BLOCK + } + } else if (obj_id == SPIFFS_OBJ_ID_ERASED) { + fs->stats_p_deleted++; + } else { + fs->stats_p_allocated++; + } + + return SPIFFS_COUNTINUE; +} + +// Scans thru all obj lu and counts free, deleted and used pages +s32_t spiffs_obj_lu_scan( + spiffs *fs) { + s32_t res; + spiffs_block_ix bix; + int entry; + + fs->free_blocks = 0; + fs->stats_p_allocated = 0; + fs->stats_p_deleted = 0; + + res = spiffs_obj_lu_find_entry_visitor(fs, + 0, + 0, + 0, + 0, + spiffs_obj_lu_scan_v, + 0, + 0, + &bix, + &entry); + + SPIFFS_CHECK_RES(res); + + return res; +} + +// Find free object lookup entry +// Iterate over object lookup pages in each block until a free object id entry is found +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + spiffs_block_ix *block_ix, + int *index_entry) { + s32_t res; + res = spiffs_obj_lu_find_id(fs, starting_block, starting_index_entry, + SPIFFS_OBJ_ID_FREE, block_ix, index_entry); + if (res == SPIFFS_OK) { + fs->free_cursor_block_ix = *block_ix; + fs->free_cursor_obj_lu_entry = *index_entry; + if (*index_entry == 0) { + fs->free_blocks--; + } + } + if (res == SPIFFS_ERR_NOT_FOUND) { + SPIFFS_DBG("fs full\n"); + } + return res == SPIFFS_ERR_NOT_FOUND ? SPIFFS_ERR_FULL : res; +} + +// Find object lookup entry containing given id +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *index_entry) { + s32_t res = spiffs_obj_lu_find_entry_visitor( + fs, starting_block, starting_index_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, index_entry); + return res; +} + + +static s32_t spiffs_obj_lu_find_id_and_index_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + u32_t user_data, + void *user_p) { + s32_t res; + spiffs_page_header ph; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + if (ph.span_ix == (spiffs_span_ix)user_data && ((ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) == SPIFFS_PH_FLAG_DELET)) { + return SPIFFS_OK; + } else { + return SPIFFS_COUNTINUE; + } +} + +// Find object lookup entry containing given id and span index +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_index( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_ID, + obj_id, + spiffs_obj_lu_find_id_and_index_v, + (u32_t)spix, + 0, + &bix, + &entry); + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +// Allocates a free defined page with given obj_id +// Occupies object lookup entry and page +// data may be NULL; where only page header is stored, len and page_offs is ignored +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + int entry; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&ph->obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write page header + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_header), (u8_t*)ph); + SPIFFS_CHECK_RES(res); + + // write page data + if (data) { + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0,SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + sizeof(spiffs_page_header) + page_offs, len, data); + SPIFFS_CHECK_RES(res); + } + + // finalize header if necessary + if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) { + ph->flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&ph->flags); + SPIFFS_CHECK_RES(res); + } + + // return written page + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} + +// Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page. +// If page data is null, provided header is used for metainfo and page data is physically copied. +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix) { + s32_t res; + u8_t was_final = 0; + spiffs_page_header *p_hdr; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + if (dst_pix) *dst_pix = free_pix; + + p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr; + if (page_data) { + // got page data + was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0; + // write unfinalized page + p_hdr->flags |= SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data); + } else { + // copy page data + res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs)); + } + SPIFFS_CHECK_RES(res); + + // mark entry in destination object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&p_hdr->obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + if (was_final) { + // mark finalized in destination page + p_hdr->flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fh, + SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr->flags); + SPIFFS_CHECK_RES(res); + } + // mark source deleted + res = spiffs_page_delete(fs, src_pix); + return res; +} + +// Deletes a page and removes it from object lookup. +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix) { + s32_t res; + spiffs_page_header hdr; + hdr.flags = 0xff & ~SPIFFS_PH_FLAG_DELET; + // mark deleted entry in source object lookup + spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_ERASED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, + 0, + SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&d_obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_deleted++; + fs->stats_p_allocated--; + + // mark deleted in source page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, + 0, + SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&hdr.flags); + return res; +} + +// Create an object index header page with empty index and undefined length +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + spiffs_page_object_ix_header oix_hdr; + int entry; + + res = spiffs_gc_check(fs, 0); + SPIFFS_CHECK_RES(res); + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("found free page @ %04x bix:%i entry:%i\n", SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write empty object index page + oix_hdr.p_hdr.obj_id = obj_id; + oix_hdr.p_hdr.span_ix = 0; + oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + oix_hdr.type = type; + oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page + strncpy((char *)&oix_hdr.name, (char *)name, SPIFFS_OBJ_NAME_LEN); + + + // update page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr); + + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); + + if (objix_hdr_pix) { + *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} + +// update object index header with any combination of name/size/index +// new_objix_hdr_data may be null, if so the object index header page is loaded +// name may be null, if so name is not changed +// size may be null, if so size is not changed +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + u8_t name[SPIFFS_OBJ_NAME_LEN], + u32_t size, + spiffs_page_ix *new_pix) { + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header *objix_hdr; + spiffs_page_ix new_objix_hdr_pix; + + if (new_objix_hdr_data) { + // object index header page already given to us, no need to load it + objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data; + } else { + // read object index header page + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + objix_hdr = (spiffs_page_object_ix_header *)fs->work; + } + + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, objix_hdr->p_hdr.obj_id, 0); + + // change name + if (name) { + strncpy((char *)objix_hdr->name, (char *)name, SPIFFS_OBJ_NAME_LEN); + } + if (size) { + objix_hdr->size = size; + } + + // move and update page + res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, 0, objix_hdr_pix, &new_objix_hdr_pix); + + if (res == SPIFFS_OK) { + if (new_pix) { + *new_pix = new_objix_hdr_pix; + } + // callback on object index update + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix_hdr->p_hdr.obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); + if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster + } + + return res; +} + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_fd *fd, + int ev, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size) { + // update index caches in all file descriptors + obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + int i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; + if (spix == 0) { + if (ev == SPIFFS_EV_IX_NEW || ev == SPIFFS_EV_IX_UPD) { + SPIFFS_DBG(" callback: setting fd %i:%04x objix_hdr_pix to %04x, size:%i\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size); + cur_fd->objix_hdr_pix = new_pix; + if (new_size != 0) { + cur_fd->size = new_size; + } + } else if (ev == SPIFFS_EV_IX_DEL) { + cur_fd->file_nbr = 0; + cur_fd->obj_id = SPIFFS_OBJ_ID_ERASED; + } + } + if (cur_fd->cursor_objix_spix == spix) { + if (ev == SPIFFS_EV_IX_NEW || ev == SPIFFS_EV_IX_UPD) { + SPIFFS_DBG(" callback: setting fd %i:%04x span:%04x objix_pix to %04x\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix); + cur_fd->cursor_objix_pix = new_pix; + } else { + cur_fd->cursor_objix_pix = 0; + } + } + } +} + +// Open object by id +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *fd, + spiffs_attr attr, + spiffs_mode mode) { + s32_t res = SPIFFS_OK; + spiffs_page_ix pix; + + res = spiffs_obj_lu_find_id_and_index(fs, obj_id, 0, &pix); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_open_by_page(fs, pix, fd, attr, mode); + + return res; +} + +// Open object by page index +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *fd, + spiffs_attr attr, + spiffs_mode mode) { + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header oix_hdr; + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr); + SPIFFS_CHECK_RES(res); + + fd->fs = fs; + fd->objix_hdr_pix = pix; + fd->size = oix_hdr.size; + fd->offset = 0; + fd->cursor_objix_pix = pix; + fd->cursor_objix_spix = 0; + fd->obj_id = oix_hdr.p_hdr.obj_id; + fd->mode = mode; + + SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0); + + SPIFFS_DBG("open: fd %i is obj id %04x\n", fd->file_nbr, fd->obj_id); + + return res; +} + +// Append to object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + res = spiffs_gc_check(fs, len); + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_page; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_page; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index page, unless first pass + SPIFFS_DBG("append: %04x store objix %04x:%04x, written %i\n", fd->obj_id, + cur_objix_pix, prev_objix_spix, written); + if (prev_objix_spix == 0) { + // this is an update to object index header page + objix_hdr->size = offset+written; + if (offset == 0) { + // was an empty object, update same page (size was 0xffffffff) + res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + } else { + // was a nonempty object, update to new page + res = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, fs->work, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: %04x store new objix_hdr, %04x:%04x, written %i\n", fd->obj_id, + new_objix_hdr_page, 0, written); + } + } else { + // this is an update to an object index page + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix->p_hdr.obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + // update length in object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: %04x store new size %i in objix_hdr, %04x:%04x, written %i\n", fd->obj_id, + offset+written, new_objix_hdr_page, 0, written); + } + fd->size = offset+written; + fd->offset = offset+written; + } + + // create or load new object index page + if (cur_objix_spix == 0) { + // load object index header page, must always exist + SPIFFS_DBG("append: %04x load objixhdr page %04x:%04x\n", fd->obj_id, cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs)); + // on subsequent passes, create a new object index page + if (written > 0 || cur_objix_spix > len_objix_spix) { + p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = cur_objix_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + res = spiffs_page_allocate_data(fs, &p_hdr, 0, 0, 0, 1, &cur_objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); + // quick "load" of new object index page + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header)); + SPIFFS_DBG("append: %04x create objix page, %04x:%04x, written %i\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + } else { + // on first pass, we load existing object index page + spiffs_page_ix pix; + SPIFFS_DBG("append: %04x find objix span_ix:%04x\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_index(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("append: %04x found object index at page %04x\n", fd->obj_id, pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + fd->size = offset+written; + } + prev_objix_spix = cur_objix_spix; + } + + // write data + u32_t to_spiffs_wr = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + if (page_offs == 0) { + // at beginning of a page, allocate and write a new page of data + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately + res = spiffs_page_allocate_data(fs, &p_hdr, &data[written], to_spiffs_wr, page_offs, 1, &data_page); + SPIFFS_DBG("append: %04x store new data page, %04x:%04x offset:%i, len %i, written %i\n", fd->obj_id, + data_page, data_spix, page_offs, to_spiffs_wr, written); + } else { + // append to existing page, fill out free data in existing page + if (cur_objix_spix == 0) { + // get data page from object index header page + data_page = ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_page = ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + res = spiffs_page_data_check(fs, fd, data_page, data_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_spiffs_wr, &data[written]); + SPIFFS_DBG("append: %04x store to existing data page, %04x:%04x offset:%i, len %i, written %i\n", fd->obj_id + , data_page, data_spix, page_offs, to_spiffs_wr, written); + } + + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page; + SPIFFS_DBG("append: %04x wrote page %04x to objix_hdr entry %02x in mem\n", fd->obj_id + , data_page, data_spix); + objix_hdr->size = offset+written; + } else { + // update object index page + ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page; + SPIFFS_DBG("append: %04x wrote page %04x to objix entry %02x in mem\n", fd->obj_id + , data_page, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_spiffs_wr; + } // while all data + + fd->size = offset+written; + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page, unless object header index page + SPIFFS_DBG("append: %04x store objix page, %04x:%04x, written %i\n", fd->obj_id, + cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix->p_hdr.obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + + // update size in object header index page + SPIFFS_DBG("append: %04x store new size %i in objix_hdr, %04x:%04x, written %i\n", fd->obj_id + , offset+written, new_objix_hdr_page, 0, written); + res2 = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res2); + } else { + // wrote within object index header page + if (offset == 0) { + // wrote to empty object - simply update size and write whole page + objix_hdr->size = offset+written; + SPIFFS_DBG("append: %04x store fresh objix_hdr page, %04x:%04x, written %i\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + // callback on object index update + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix_hdr->p_hdr.obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); + } else { + // modifying object index header page, update size and make new copy + res2 = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, fs->work, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: %04x store modified objix_hdr page, %04x:%04x, written %i\n", fd->obj_id + , new_objix_hdr_page, 0, written); + SPIFFS_CHECK_RES(res2); + } + } + + return res; +} + +// Modify object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + res = spiffs_gc_check(fs, len); + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_pix; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_pix; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index (header) page, unless first pass + if (prev_objix_spix == 0) { + // store previous object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, fs->work, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, %04x:%04x, written %i\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res); + } else { + // store new version of previous object index page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store previous modified objix page, %04x:%04x, written %i\n", new_objix_pix, objix->p_hdr.span_ix, written); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix->p_hdr.obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + + // load next object index page + if (cur_objix_spix == 0) { + // load object index header page, must exist + SPIFFS_DBG("modify: load objixhdr page %04x:%04x\n", cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + // load existing object index page on first pass + spiffs_page_ix pix; + SPIFFS_DBG("modify: find objix span_ix:%04x\n", cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_index(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("modify: found object index at page %04x\n", pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + prev_objix_spix = cur_objix_spix; + } + + // write partial data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + spiffs_page_ix orig_data_pix; + if (cur_objix_spix == 0) { + // get data page from object index header page + orig_data_pix = ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + orig_data_pix = ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + p_hdr.flags = 0xff; + if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) { + // a full page, allocate and write a new page of data + res = spiffs_page_allocate_data(fs, &p_hdr, &data[written], to_write, page_offs, 1, &data_pix); + SPIFFS_DBG("modify: store new data page, %04x:%04x offset:%i, len %i, written %i\n", data_pix, data_spix, page_offs, to_write, written); + } else { + // write to existing page, allocate new and copy unmodified data + + res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_allocate_data(fs, &p_hdr, 0, 0, 0, 0, &data_pix); + if (res != SPIFFS_OK) break; + + // copy unmodified data + if (page_offs > 0) { + // before modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header), + page_offs); + if (res != SPIFFS_OK) break; + } + if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) { + // after modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write)); + if (res != SPIFFS_OK) break; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + SPIFFS_DBG("modify: store to existing data page, src:%04x, dst:%04x:%04x offset:%i, len %i, written %i\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); + } + + // delete original data page + res = spiffs_page_delete(fs, orig_data_pix); + if (res != SPIFFS_OK) break; + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix; + SPIFFS_DBG("modify: wrote page %04x to objix_hdr entry %02x in mem\n", data_pix, data_spix); + } else { + // update object index page + ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix; + SPIFFS_DBG("modify: wrote page %04x to objix entry %02x in mem\n", data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page + // move and update page + spiffs_page_ix new_objix_pix; + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store modified objix page, %04x:%04x, written %i\n", new_objix_pix, cur_objix_spix, written); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix->p_hdr.obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + + } else { + // wrote within object index header page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, fs->work, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, %04x:%04x, written %i\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res2); + } + + return res; +} + +static s32_t spiffs_object_find_object_index_header_by_name_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + u32_t user_data, + void *user_p) { + s32_t res; + spiffs_page_object_ix_header objix_hdr; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_ERASED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_COUNTINUE; + } + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if ((objix_hdr.p_hdr.obj_id & SPIFFS_OBJ_ID_IX_FLAG) && + objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags& (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL)) == SPIFFS_PH_FLAG_DELET) { + if (strcmp((char *)user_p, (char *)objix_hdr.name) == 0) { + return SPIFFS_OK; + } + } + + return SPIFFS_COUNTINUE; +} + +// Finds object index header page by name +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + 0, + 0, + spiffs_object_find_object_index_header_by_name_v, + 0, + name, + &bix, + &entry); + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +// Truncates object to new size. If new size is null, object may be removed totally +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_size, + u8_t remove) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + + res = spiffs_gc_check(fs, 0); + SPIFFS_CHECK_RES(res); + + spiffs_page_ix objix_pix = fd->objix_hdr_pix; + spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_size = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size ; + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_ix data_pix; + spiffs_page_ix new_objix_hdr_pix; + + // delete from end of object until desired len is reached + while (cur_size > new_size) { + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // put object index for current data span index in work buffer + if (prev_objix_spix != cur_objix_spix) { + if (prev_objix_spix != (spiffs_span_ix)-1) { + // remove previous object index page + SPIFFS_DBG("truncate: delete objix page %04x:%04x\n", objix_pix, prev_objix_spix); + + res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_DEL, objix->p_hdr.obj_id, objix->p_hdr.span_ix, objix_pix, 0); + if (prev_objix_spix > 0) { + // update object index header page + SPIFFS_DBG("truncate: update objix hdr page %04x:%04x to size %i\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); + res = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + fd->size = cur_size; + } + } + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + res = spiffs_obj_lu_find_id_and_index(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, &objix_pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("truncate: load objix page %04x:%04x for data spix:%04x\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE; + } + + if (cur_size - SPIFFS_DATA_PAGE_SIZE(fs) >= new_size) { + // delete full data page + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_OK) break; + + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) break; + // update current size + if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) { + cur_size -= SPIFFS_DATA_PAGE_SIZE(fs); + } else { + cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs); + } + fd->size = cur_size; + fd->offset = cur_size; + SPIFFS_DBG("truncate: delete data page %04x for data spix:%04x, cur_size:%i\n", data_pix, data_spix, cur_size); + } else { + // delete last page, partially + spiffs_page_header p_hdr; + spiffs_page_ix new_data_pix; + u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_DBG("truncate: delete %i bytes from data page %04x for data spix:%04x, cur_size:%i\n", bytes_to_remove, data_pix, data_spix, cur_size); + + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_OK) break; + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + // allocate new page and copy unmodified data + res = spiffs_page_allocate_data(fs, &p_hdr, 0, 0, 0, 0, &new_data_pix); + if (res != SPIFFS_OK) break; + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove); + if (res != SPIFFS_OK) break; + // delete original data page + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + SPIFFS_DBG("truncate: wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } else { + // update object index page + ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + SPIFFS_DBG("truncate: wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + cur_size = new_size; + fd->size = new_size; + fd->offset = cur_size; + break; + } + data_spix--; + } // while all data + + // update object indices + if (cur_objix_spix == 0) { + // update object index header page + if (cur_size == 0) { + if (remove) { + // remove object altogether + SPIFFS_DBG("truncate: remove object index header page %04x\n", objix_pix); + + res = spiffs_page_index_check(fs, fd, objix_pix, 0); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); + } else { + // make uninitialized object + SPIFFS_DBG("truncate: reset objix_hdr page %04x\n", objix_pix); + memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff, + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header)); + res = spiffs_object_update_index_hdr(fs, fd, objix_pix, fs->work, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update object index header page + SPIFFS_DBG("truncate: update object index header page with indices and size\n"); + res = spiffs_object_update_index_hdr(fs, fd, objix_pix, fs->work, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update both current object index page and object index header page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res); + + // move and update object index page + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, 0, objix_pix, &new_objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, objix->p_hdr.obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + SPIFFS_DBG("truncate: store modified objix page, %04x:%04x\n", new_objix_pix, cur_objix_spix); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + // update object index header page with new size + res = spiffs_object_update_index_hdr(fs, fd, fd->objix_hdr_pix, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + + return res; +} + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + spiffs_page_ix objix_pix; + spiffs_page_ix data_pix; + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_offset = offset; + spiffs_span_ix cur_objix_spix; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + while (cur_offset < offset + len) { + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (prev_objix_spix != cur_objix_spix) { + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + res = spiffs_obj_lu_find_id_and_index(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, &objix_pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("read: load objix page %04x:%04x for data spix:%04x\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); + + fd->offset = cur_offset; + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + // all remaining data + u32_t len_to_read = offset + len - cur_offset; + // remaining data in page + len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + // remaining data in file + len_to_read = MIN(len_to_read, fd->size); + SPIFFS_DBG("read: offset:%i rd:%i data spix:%04x is data_pix:%04x addr:%08x\n", cur_offset, len_to_read, data_spix, data_pix, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + if (len_to_read <= 0) { + res = SPIFFS_ERR_END_OF_OBJECT; + break; + } + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + SPIFFS_CHECK_RES(res); + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)), + len_to_read, + dst); + SPIFFS_CHECK_RES(res); + dst += len_to_read; + cur_offset += len_to_read; + fd->offset = cur_offset; + data_spix++; + } + + return res; +} + +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len) { + s32_t res; + u32_t free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * fs->block_count + - fs->stats_p_allocated - fs->stats_p_deleted; + int tries = 0; + + if (fs->free_blocks > 2 && + len < free_pages * SPIFFS_DATA_PAGE_SIZE(fs)) { + return SPIFFS_OK; + } + + do { + SPIFFS_GC_DBG("\ngc_check #i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", + tries, + fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), + len, free_pages*SPIFFS_DATA_PAGE_SIZE(fs)); + + spiffs_block_ix *cands; + int count; + spiffs_block_ix cand; + res = spiffs_gc_find_candidate(fs, &cands, &count); + SPIFFS_CHECK_RES(res); + if (count == 0) { + SPIFFS_GC_DBG("gc_check: no candidates, return\n"); + return res; + } +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + cand = cands[0]; + res = spiffs_gc_clean(fs, cand); + SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_page_stats(fs, cand); + SPIFFS_CHECK_RES(res); + + u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, cand); + s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + // here we ignore res, just try erasing the block + SPIFFS_GC_DBG("gc_check: erase block %i\n", cand); + while (size > 0) { + SPIFFS_DBG("gc_check: erase %08x:%08x\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + res = fs->cfg.hal_erase_f(addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs); + size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs); + } + fs->free_blocks++; + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * fs->block_count + - fs->stats_p_allocated - fs->stats_p_deleted; +#if SPIFFS_CACHE + { + int i; + for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) { + spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, cand) + i); + } + } +#endif + } while (tries++ < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || + len > free_pages*SPIFFS_DATA_PAGE_SIZE(fs))); + SPIFFS_GC_DBG("gc_check: finished\n"); + + return res; +} + +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + int obj_lookup_page = 0; + u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + u32_t dele = 0; + u32_t allo = 0; + + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + } else if (obj_id == SPIFFS_OBJ_ID_ERASED) { + dele++; + } else { + allo++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + SPIFFS_GC_DBG("gc_check: wipe pallo:%i pdele:%i\n", allo, dele); + fs->stats_p_allocated -= allo; + fs->stats_p_deleted -= dele; + return res; +} + +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidates, + int *candidate_count) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = SPIFFS_CFG_PHYS_ADDR(fs); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + + // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(int)score + int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(int))); + *candidate_count = 0; + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + // divide up work area into block indices and scores + // todo alignment? + spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; + int *cand_scores = (int *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); + + *block_candidates = cand_blocks; + + u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t used_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + // when a free entry is encountered, scan logic ensures that all following entries are free also + break; + } else if (obj_id == SPIFFS_OBJ_ID_ERASED) { + deleted_pages_in_block++; + } else { + used_pages_in_block++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + + // calculate score and insert into candidate table + // stoneage sort, but probably not so many blocks + if (res == SPIFFS_OK && deleted_pages_in_block > 0) { + int score = deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET + used_pages_in_block * SPIFFS_GC_HEUR_W_USED; + int cand_ix = 0; + SPIFFS_DBG("gc_check: bix:%i del:%i use:%i score:%i\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); + while (cand_ix < max_candidates) { + if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } else if (cand_scores[cand_ix] < score) { + int reorder_cand_ix = max_candidates - 2; + while (reorder_cand_ix >= cand_ix) { + cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix]; + cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix]; + reorder_cand_ix--; + } + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } + cand_ix++; + } + (*candidate_count)++; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + return res; +} + +typedef enum { + MOVE_OBJ_IX, + FIND_OBJ_DATA, + MOVE_OBJ_DATA, + FINISHED +} spiffs_gc_clean_state; + +typedef struct { + spiffs_gc_clean_state state; + spiffs_obj_id cur_obj_id; + spiffs_span_ix cur_objix_spix; + spiffs_page_ix cur_objix_pix; + int stored_scan_entry_index; + u8_t obj_id_found; +} spiffs_gc; + +// Empties given block by moving all data into free pages of another block +// Strategy: +// scan object lookup for object index pages, move to new page in other block +// loop: +// scan object lookup for object data pages +// for first found id, check spix and load corresponding object index page to memory +// push object scan lookup entry index +// rescan object lookup, find data pages with same id and referenced by same object index +// move data page, update object index in memory +// when reached end of lookup, store updated object index +// pop object scan lookup entry index +// repeat loop until end of object lookup +// +s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_gc gc; + spiffs_page_ix cur_pix = 0; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + memset(&gc, 0, sizeof(spiffs_gc)); + gc.state = MOVE_OBJ_IX; + + if (fs->free_cursor_block_ix == bix) { + // move free cursor to next block + fs->free_cursor_block_ix = (bix+1)%fs->block_count; + fs->free_cursor_obj_lu_entry = 0; + SPIFFS_DBG("gc_clean: move free cursor to block %i\n", fs->free_cursor_block_ix); + } + + while (res == SPIFFS_OK && gc.state != FINISHED) { + SPIFFS_DBG("gc_clean: state = %i entry:%i\n", gc.state, cur_entry); + gc.obj_id_found = 0; + + // scan through lookup pages + int obj_lookup_page = cur_entry / entries_per_page; + u8_t scan = 1; + // check each object lookup page + while (scan && res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (scan && res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); + + // act upon object id depending on gc state + switch (gc.state) { + case MOVE_OBJ_IX: + if (obj_id != SPIFFS_OBJ_ID_ERASED && obj_id != SPIFFS_OBJ_ID_FREE && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + // found an index object id + spiffs_page_header p_hdr; + spiffs_page_ix new_pix; + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, &p_hdr, cur_pix, &new_pix); + SPIFFS_DBG("gc_clean: MOVE_OBJIX move objix %04x:%04x page %04x to %04x\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, obj_id, p_hdr.span_ix, new_pix, 0); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it + SPIFFS_DBG("gc_clean: MOVE_OBJIX wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + if (res == SPIFFS_OK) { + spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); + } + } + SPIFFS_CHECK_RES(res); + } + break; + case FIND_OBJ_DATA: + if (obj_id != SPIFFS_OBJ_ID_ERASED && obj_id != SPIFFS_OBJ_ID_FREE) { + SPIFFS_DBG("gc_clean: FIND_DATA state:%i - found obj id %04x\n", gc.state, obj_id); + gc.obj_id_found = 1; + gc.cur_obj_id = obj_id; + scan = 0; + } + break; + case MOVE_OBJ_DATA: + if (obj_id == gc.cur_obj_id) { + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("gc_clean: MOVE_DATA found data page %04x:%04x @ %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { + SPIFFS_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + } else { + spiffs_page_ix new_data_pix; + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, &p_hdr, cur_pix, &new_data_pix); + SPIFFS_DBG("gc_clean: MOVE_DATA move objix %04x:%04x page %04x to %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); + SPIFFS_CHECK_RES(res); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it + SPIFFS_DBG("gc_clean: MOVE_DATA wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + new_data_pix = SPIFFS_OBJ_ID_FREE; + } + // update memory representation of object index page with new data page + if (gc.cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((void*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; + SPIFFS_DBG("gc_clean: MOVE_DATA wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } else { + // update object index page + ((spiffs_page_ix*)((void*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; + SPIFFS_DBG("gc_clean: MOVE_DATA wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } + } + } + break; + default: + scan = 0; + break; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + + if (res != SPIFFS_OK) break; + + // state finalization and switch + switch (gc.state) { + case MOVE_OBJ_IX: + gc.state = FIND_OBJ_DATA; + cur_entry = 0; // restart entry scan index + break; + case FIND_OBJ_DATA: + if (gc.obj_id_found) { + // find out corresponding obj ix page and load it to memory + spiffs_page_header p_hdr; + spiffs_page_ix objix_pix; + gc.stored_scan_entry_index = cur_entry; + cur_entry = 0; + gc.state = MOVE_OBJ_DATA; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); + SPIFFS_DBG("gc_clean: FIND_DATA find objix span_ix:%04x\n", gc.cur_objix_spix); + res = spiffs_obj_lu_find_id_and_index(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, &objix_pix); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("gc_clean: FIND_DATA found object index at page %04x\n", objix_pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id, gc.cur_objix_spix); + gc.cur_objix_pix = objix_pix; + } else { + gc.state = FINISHED; + } + break; + case MOVE_OBJ_DATA: { + // store modified objix (hdr) page + spiffs_page_ix new_objix_pix; + gc.state = FIND_OBJ_DATA; + cur_entry = gc.stored_scan_entry_index; + if (gc.cur_objix_spix == 0) { + // store object index header page + res = spiffs_object_update_index_hdr(fs, 0, gc.cur_objix_pix, fs->work, 0, 0, &new_objix_pix); + SPIFFS_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, %04x:%04x\n", new_objix_pix, 0); + SPIFFS_CHECK_RES(res); + } else { + // store object index page + spiffs_page_ix new_objix_pix; + res = spiffs_page_move(fs, 0, fs->work, 0, gc.cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("gc_clean: MOVE_DATA store modified objix page, %04x:%04x\n", new_objix_pix, objix->p_hdr.span_ix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + break; + default: + cur_entry = 0; + break; + } + SPIFFS_DBG("gc_clean: state-> %i\n", gc.state); + } // while state != FINISHED + + + return res; +} + +typedef struct { + spiffs_obj_id min_obj_id; + spiffs_obj_id max_obj_id; + u32_t compaction; +} spiffs_free_obj_id_state; + +static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + u32_t user_data, void *user_p) { + if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_ERASED) { + spiffs_obj_id min_obj_id = user_data; + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + int bit_ix = (id-min_obj_id) & 7; + int byte_ix = (id-min_obj_id) >> 3; + if (byte_ix >= 0 && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + fs->work[byte_ix] |= (1<= state->min_obj_id && id <= state->max_obj_id) { + u8_t *map = (u8_t *)fs->work; + int ix = (id - state->min_obj_id) / state->compaction; + //SPIFFS_DBG("free_obj_id: add ix %i for id %04x min:%04x max%04x comp:%i\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); + map[ix]++; + } + } + } + return SPIFFS_COUNTINUE; +} + +s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id) { + s32_t res = SPIFFS_OK; + u32_t max_objects = (SPIFFS_CFG_PHYS_SZ(fs) / (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) / 2; + spiffs_free_obj_id_state state; + spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE; + state.min_obj_id = 1; + state.max_obj_id = max_objects + 1; + if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) { + state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG; + } + state.compaction = 0; + while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) { + if (state.max_obj_id - state.min_obj_id <= SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) { + // possible to represent in bitmap + int i, j; + SPIFFS_DBG("free_obj_id: BITM min:%04x max:%04x\n", state.min_obj_id, state.max_obj_id); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, state.min_obj_id, 0, 0, 0); + if (res == SPIFFS_ERR_NOT_FOUND) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + // traverse bitmask until found free obj_id + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) { + u8_t mask = fs->work[i]; + if (mask == 0xff) { + continue; + } + for (j = 0; j < 8; j++) { + if ((mask & (1<work; + u8_t min_count = 0xff; + + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) { + if (map[i] < min_count) { + min_count = map[i]; + min_i = i; + if (min_count == 0) { + break; + } + } + } + + if (min_count == state.compaction) { + // there are no free objids! + SPIFFS_DBG("free_obj_id: compacted table is full\n"); + return SPIFFS_ERR_FULL; + } + + SPIFFS_DBG("free_obj_id: COMP select index:%i min_count:%i min:%04x max:%04x compact:%i\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); + + if (min_count == 0) { + // no id in this range, skip compacting and use directly + *obj_id = min_i * state.compaction + state.min_obj_id; + return SPIFFS_OK; + } else { + SPIFFS_DBG("free_obj_id: COMP SEL chunk:%04x min:%04x -> %04x\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); + state.min_obj_id += min_i * state.compaction; + state.max_obj_id = state.min_obj_id + state.compaction; + // decrease compaction + } + if ((state.max_obj_id - state.min_obj_id <= SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) { + // no need for compacting, use bitmap + continue; + } + } + // in a work memory of log_page_size bytes, we may fit in log_page_size ids + // todo what if compaction is > 255 - then we cannot fit it in a byte + state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t))); + SPIFFS_DBG("free_obj_id: COMP min:%04x max:%04x compact:%i\n", state.min_obj_id, state.max_obj_id, state.compaction); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, 0, &state, 0, 0); + if (res == SPIFFS_ERR_NOT_FOUND) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + } + } + + return res; +} + +s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd) { + int i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + cur_fd->file_nbr = i+1; + *fd = cur_fd; + return SPIFFS_OK; + } + } + return SPIFFS_ERR_OUT_OF_FILE_DESCS; +} + +s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { + if (f <= 0 || f > fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + spiffs_fd *fd = &fds[f-1]; + if (fd->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + fd->file_nbr = 0; + return SPIFFS_OK; +} + +s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) { + if (f <= 0 || f > fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + *fd = &fds[f-1]; + if ((*fd)->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + return SPIFFS_OK; +} diff --git a/src/spiffs_nucleus.h b/src/spiffs_nucleus.h new file mode 100644 index 0000000..bd5b9c9 --- /dev/null +++ b/src/spiffs_nucleus.h @@ -0,0 +1,584 @@ +/* + * spiffs_nucleus.h + * + * Created on: Jun 15, 2013 + * Author: petera + */ + +/* SPIFFS layout + * + * spiffs is designed for following spi flash characteristics: + * - only big areas of data (blocks) can be erased + * - erasing resets all bits in a block to ones + * - writing pulls ones to zeroes + * - zeroes cannot be pulled to ones, without erase + * - wear leveling + * + * spiffs is also meant to be run on embedded, memory constraint devices. + * + * Entire area is divided in blocks. Entire area is also divided in pages. + * Each block contains same number of pages. A page cannot be erased, but a + * block can be erased. + * + * Entire area must be block_size * x + * page_size must be block_size / (2^y) where y > 2 + * + * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes + * + * BLOCK 0 PAGE 0 object lookup 1 + * PAGE 1 object lookup 2 + * ... + * PAGE n-1 object lookup n + * PAGE n object data 1 + * PAGE n+1 object data 2 + * ... + * PAGE n+m-1 object data m + * + * BLOCK 1 PAGE n+m object lookup 1 + * PAGE n+m+1 object lookup 2 + * ... + * PAGE 2n+m-1 object lookup n + * PAGE 2n+m object data 1 + * PAGE 2n+m object data 2 + * ... + * PAGE 2n+2m-1 object data m + * ... + * + * n is number of object lookup pages, which is number of pages needed to index all pages + * in a block by object id + * : block_size / page_size * sizeof(obj_id) / page_size + * m is number data pages, which is number of pages in block minus number of lookup pages + * : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size + * thus, n+m is total number of pages in a block + * : block_size / page_size + * + * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256 + * + * Object lookup pages contain object id entries. Each entry represent the corresponding + * data page. + * Assuming a 16 bit object id, an object id being 0xffff represents a free page. + * An object id being 0x0000 represents a deleted page. + * + * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff .. + * page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff .. + * page 2 : data : data for object id 0008 + * page 3 : data : data for object id 0001 + * page 4 : data : data for object id 0aaa + * ... + * + * + * Object data pages can be either object index pages or object content. + * All object data pages contains a data page header, containing object id and span index. + * The span index denotes the object page ordering amongst data pages with same object id. + * This applies to both object index pages (when index spans more than one page of entries), + * and object data pages. + * An object index page contains page entries pointing to object content page. The entry index + * in a object index page correlates to the span index in the actual object data page. + * The first object index page (span index 0) is called object index header page, and also + * contains object flags (directory/file), size, object name etc. + * + * ex: + * BLOCK 1 + * PAGE 256: objectl lookup page 1 + * [*123] [ 123] [ 123] [ 123] + * [ 123] [*123] [ 123] [ 123] + * [free] [free] [free] [free] ... + * PAGE 257: objectl lookup page 2 + * [free] [free] [free] [free] ... + * PAGE 258: object index page (header) + * obj.id:0123 span.ix:0000 flags:INDEX + * size:1600 name:ex.txt type:file + * [259] [260] [261] [262] + * PAGE 259: object data page + * obj.id:0123 span.ix:0000 flags:DATA + * PAGE 260: object data page + * obj.id:0123 span.ix:0001 flags:DATA + * PAGE 261: object data page + * obj.id:0123 span.ix:0002 flags:DATA + * PAGE 262: object data page + * obj.id:0123 span.ix:0003 flags:DATA + * PAGE 263: object index page + * obj.id:0123 span.ix:0001 flags:INDEX + * [264] [265] [fre] [fre] + * [fre] [fre] [fre] [fre] + * PAGE 264: object data page + * obj.id:0123 span.ix:0004 flags:DATA + * PAGE 265: object data page + * obj.id:0123 span.ix:0005 flags:DATA + * + */ +#ifndef SPIFFS_NUCLEUS_H_ +#define SPIFFS_NUCLEUS_H_ + +#define SPIFFS_EV_IX_UPD 0 +#define SPIFFS_EV_IX_NEW 1 +#define SPIFFS_EV_IX_DEL 2 + +#define SPIFFS_OBJ_ID_IX_FLAG (1<<(8*sizeof(spiffs_obj_id)-1)) + +#define SPIFFS_UNDEFINED_LEN (-1) + +#define SPIFFS_OBJ_ID_ERASED ((spiffs_obj_id)0) +#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) + +#ifndef SPIFFS_SINGLETON +#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \ + ((fs)->cfg.log_page_size) +#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \ + ((fs)->cfg.log_block_size) +#define SPIFFS_CFG_PHYS_SZ(fs) \ + ((fs)->cfg.phys_size) +#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \ + ((fs)->cfg.phys_erase_block) +#define SPIFFS_CFG_PHYS_ADDR(fs) \ + ((fs)->cfg.phys_addr) +#endif + +// total number of pages per block, including object lookup pages +#define SPIFFS_PAGES_PER_BLOCK(fs) \ + ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// number of object lookup pages per block +#define SPIFFS_OBJ_LOOKUP_PAGES(fs) \ + (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) ) +// checks if page index belongs to object lookup +#define SPIFFS_IS_LOOKUP_PAGE(fs,pix) \ + (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// number of object lookup entries in all object lookup pages +#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \ + (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// converts a block to physical address +#define SPIFFS_BLOCK_TO_PADDR(fs, block) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) ) +// converts a object lookup entry to page index +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \ + ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry)) +// converts a object lookup entry to physical address of corresponding page +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \ + (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a page to physical address +#define SPIFFS_PAGE_TO_PADDR(fs, page) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a physical address to page +#define SPIFFS_PADDR_TO_PAGE(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// gives index in page for a physical address +#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// returns containing block for given page +#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \ + ((page) / SPIFFS_PAGES_PER_BLOCK(fs)) +// returns starting page for block +#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \ + ((block) * SPIFFS_PAGES_PER_BLOCK(fs)) +// converts page to entry in object lookup page +#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \ + ((page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// returns data size in a data page +#define SPIFFS_DATA_PAGE_SIZE(fs) \ + ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) ) + +// define helpers object + +// entries in an object header page index +#define SPIFFS_OBJ_HDR_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix)) +// entries in an object page index +#define SPIFFS_OBJ_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix)) +// object index entry for given data span index +#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs))) +// object index span index number for given data span index or entry +#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) + + +#define SPIFFS_OP_T_OBJ_LU (0<<0) +#define SPIFFS_OP_T_OBJ_LU2 (1<<0) +#define SPIFFS_OP_T_OBJ_IX (2<<0) +#define SPIFFS_OP_T_OBJ_DA (3<<0) +#define SPIFFS_OP_C_DELE (0<<2) +#define SPIFFS_OP_C_UPDT (1<<2) +#define SPIFFS_OP_C_MOVS (2<<2) +#define SPIFFS_OP_C_MOVD (3<<2) +#define SPIFFS_OP_C_FLSH (4<<2) +#define SPIFFS_OP_C_READ (5<<2) + +#define SPIFFS_OP_TYPE_MASK (3<<0) +#define SPIFFS_OP_COM_MASK (7<<2) + + +// if 0, writing is finalized, else under modification +#define SPIFFS_PH_FLAG_FINAL (1<<0) +// if 0, page is deleted, else valid +#define SPIFFS_PH_FLAG_DELET (1<<1) +// if 0, page is corrupt, else ok +#define SPIFFS_PH_FLAG_CORRU (1<<2) +// if 0, this is an index page, else a data page +#define SPIFFS_PH_FLAG_INDEX (1<<3) + + +#define SPIFFS_CHECK_RES(res) \ + do { \ + if ((res) < SPIFFS_OK) return (res); \ + } while (0); + +#define SPIFFS_API_CHECK_RES(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->errno = (res); \ + return -1; \ + } + +#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->errno = (res); \ + SPIFFS_UNLOCK(fs); \ + return -1; \ + } + +#define SPIFFS_VIS_CHECK_ID (1<<0) +#define SPIFFS_VIS_NO_WRAP (1<<1) + +#if SPIFFS_CACHE + +#define SPIFFS_CACHE_FLAG_DIRTY (1<<0) +#define SPIFFS_CACHE_FLAG_WRTHRU (1<<1) +#define SPIFFS_CACHE_FLAG_OBJLU (1<<2) +#define SPIFFS_CACHE_FLAG_OBJIX (1<<3) +#define SPIFFS_CACHE_FLAG_DATA (1<<4) +#define SPIFFS_CACHE_FLAG_TYPE_WR (1<<7) + +#define SPIFFS_CACHE_PAGE_SIZE(fs) \ + (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)) + +#define get_cache(fs) \ + ((spiffs_cache *)((fs)->cache)) + +#define get_cache_page_hdr(fs, c, ix) \ + ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)]))) + +#define get_cache_page(fs, c, ix) \ + ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page)) + +typedef struct { + u8_t flags; + u8_t ix; + u32_t last_access; + union { + struct { + // type read cache + spiffs_page_ix pix; + }; +#if SPIFFS_CACHE_WR + struct { + // type write cache + spiffs_obj_id obj_id; + u8_t refs; + u32_t offset; + u16_t size; + }; +#endif + }; +} spiffs_cache_page; + +typedef struct { + u8_t cpage_count; + u32_t last_access; + u32_t cpage_use_map; + u32_t cpage_use_mask; + u8_t *cpages; +} spiffs_cache; + +#endif + + +// spiffs nucleus file descriptor +typedef struct { + // the filesystem of this descriptor + spiffs *fs; + // number of file descriptor - if 0, the file descriptor is closed + spiffs_file file_nbr; + // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted + spiffs_obj_id obj_id; + // size of the file + u32_t size; + // cached object index header page index + spiffs_page_ix objix_hdr_pix; + // cached offset object index page index + spiffs_page_ix cursor_objix_pix; + // cached offset object index span index + spiffs_span_ix cursor_objix_spix; + // current absolute offset + u32_t offset; + // current file descriptor offset + u32_t fdoffset; + // mode + spiffs_mode mode; +#if SPIFFS_CACHE_WR + spiffs_cache_page *cache_page; +#endif +} spiffs_fd; + + +// object structs + +// page header, part of each page except object lookup pages +typedef struct __attribute(( packed )) { + // object id + spiffs_obj_id obj_id; + // object span index + spiffs_span_ix span_ix; + // flags + u8_t flags; +} spiffs_page_header; + +// object index header page header +typedef struct __attribute(( packed )) { + // common page header + spiffs_page_header p_hdr; + // alignment + u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; + // size of object + u32_t size; + // type of object + spiffs_obj_type type; + // name of object + u8_t name[SPIFFS_OBJ_NAME_LEN]; +} spiffs_page_object_ix_header; + +// object index page header +typedef struct __attribute(( packed )) { + spiffs_page_header p_hdr; + u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; +} spiffs_page_object_ix; + +// callback func for object lookup visitor + +typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + u32_t user_data, void *user_p); + + +#if SPIFFS_CACHE +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src)) +#else +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (addr), (len), (src)) +#endif +// --------------- + +s32_t spiffs_phys_rd( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *dst); + +s32_t spiffs_phys_wr( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *src); + +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len); + +s32_t spiffs_phys_count_free_blocks( + spiffs *fs); + +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + u32_t user_data, + void *user_p, + spiffs_block_ix *block_ix, + int *index_entry); + +// --------------- + +s32_t spiffs_obj_lu_scan( + spiffs *fs); + +s32_t spiffs_obj_lu_find_free_obj_id( + spiffs *fs, + spiffs_obj_id *obj_id); + +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + spiffs_block_ix *block_ix, + int *index_entry); + +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_index_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *index_entry); + +s32_t spiffs_obj_lu_find_id_and_index( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix); + +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix); + +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix); + +// --------------- + +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix); + +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + u8_t name[SPIFFS_OBJ_NAME_LEN], + u32_t size, + spiffs_page_ix *new_pix); + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_fd *fd, + int ev, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size); + +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *f, + spiffs_attr attr, + spiffs_mode mode); + +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *f, + spiffs_attr attr, + spiffs_mode mode); + +s32_t spiffs_object_append( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_modify( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst); + +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_len, + u8_t remove_object); + +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len); + +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidate, + int *candidate_count); + +s32_t spiffs_gc_clean( + spiffs *fs, + spiffs_block_ix bix); + +// --------------- + +s32_t spiffs_fd_find_new( + spiffs *fs, + spiffs_fd **fd); + +s32_t spiffs_fd_return( + spiffs *fs, + spiffs_file f); + +s32_t spiffs_fd_get( + spiffs *fs, + spiffs_file f, + spiffs_fd **fd); + +#if SPIFFS_CACHE +void spiffs_cache_init(spiffs *fs); +void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix); +#if SPIFFS_CACHE_WR +spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd); +void spiffs_cache_fh_release(spiffs *fs, spiffs_cache_page *cp); +spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd); +#endif +#endif + +void spiffs_test_list_objects(spiffs *fs); + +#endif /* SPIFFS_NUCLEUS_H_ */ diff --git a/src/stypes.h b/src/stypes.h new file mode 100644 index 0000000..f3656d8 --- /dev/null +++ b/src/stypes.h @@ -0,0 +1,28 @@ +/* + * stypes.h + * + * Created on: May 26, 2013 + * Author: petera + */ + +#ifndef STYPES_H_ +#define STYPES_H_ + +#define print(...) printf(__VA_ARGS__) + +#define ASSERT(c, m) real_assert((c),(m), __FILE__, __LINE__); + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +void real_assert(int c, const char *n, const char *file, int l); + +typedef signed int s32_t; +typedef unsigned int u32_t; +typedef signed short s16_t; +typedef unsigned short u16_t; +typedef signed char s8_t; +typedef unsigned char u8_t; + + +#endif /* STYPES_H_ */ diff --git a/src/test_hydrogen.c b/src/test_hydrogen.c new file mode 100644 index 0000000..44eb5df --- /dev/null +++ b/src/test_hydrogen.c @@ -0,0 +1,1514 @@ +/* + * test_suites.c + * + * Created on: Jun 19, 2013 + * Author: petera + */ + + +#include "testrunner.h" +#include "test_spiffs.h" +#include "spiffs_nucleus.h" +#include "spiffs.h" +#include +#include +#include +#include +#include + +//#define REMOVE_LONG_TESTS + +static void test_on_stop(test *t) { + printf(" spiffs errno:%i\n", SPIFFS_errno(&__fs)); +} + +#define CHECK(r) if (!(r)) return -1; +#define CHECK_RES(r) if (r < 0) return -1; +#define FS_PURE_DATA_PAGES(fs) \ + ((fs)->cfg.phys_size / (fs)->cfg.log_page_size - (fs)->block_count * SPIFFS_OBJ_LOOKUP_PAGES(fs)) +#define FS_PURE_DATA_SIZE(fs) \ + FS_PURE_DATA_PAGES(fs) * SPIFFS_DATA_PAGE_SIZE(fs) + +void memrand(u8_t *b, int len) { + int i; + for (i = 0; i < len; i++) { + b[i] = rand(); + } +} + +int test_create_file(char *name) { + spiffs_stat s; + spiffs_file fd; + int res = SPIFFS_creat(FS, name, 0); + CHECK_RES(res); + fd = SPIFFS_open(FS, name, 0, SPIFFS_RDONLY); + CHECK(fd >= 0); + res = SPIFFS_fstat(FS, fd, &s); + CHECK_RES(res); + CHECK(strcmp((char*)s.name, name) == 0); + CHECK(s.size == 0); + SPIFFS_close(FS, fd); + return 0; +} + +int test_create_and_write_file(char *name, int size, int chunk_size) { + int res; + spiffs_file fd; + printf(" create and write %s", name); + res = test_create_file(name); + CHECK(res >= 0); + fd = SPIFFS_open(FS, name, 0, SPIFFS_APPEND | SPIFFS_RDWR); + CHECK(fd >= 0); + int pfd = open(make_test_fname(name), O_APPEND | O_TRUNC | O_CREAT | O_RDWR); + int offset = 0; + int mark = 0; + while (offset < size) { + int len = MIN(size-offset, chunk_size); + if (offset > mark) { + mark += size/16; + printf("."); + fflush(stdout); + } + u8_t *buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + write(pfd, buf, len); + free(buf); + if (res < 0) { + printf("\n error @ offset %i, res %i\n", offset, res); + } + offset += len; + CHECK(res >= 0); + } + printf("\n"); + close(pfd); + + spiffs_stat stat; + res = SPIFFS_fstat(FS, fd, &stat); + CHECK(res >= 0); + CHECK(stat.size == size); + + SPIFFS_close(FS, fd); + return 0; +} + +#if SPIFFS_CACHE +#if SPIFFS_CACHE_STATS +static u32_t chits_tot = 0; +static u32_t cmiss_tot = 0; +#endif +#endif + +void _setup() { + fs_reset(); + test_init(test_on_stop); +} + +void _teardown() { + clear_test_path(); + printf(" free blocks : %i of %i\n", (FS)->free_blocks, (FS)->block_count); + printf(" pages allocated : %i\n", (FS)->stats_p_allocated); + printf(" pages deleted : %i\n", (FS)->stats_p_deleted); +#if SPIFFS_GC_STATS + printf(" gc runs : %i\n", (FS)->stats_gc_runs); +#endif +#if SPIFFS_CACHE +#if SPIFFS_CACHE_STATS + chits_tot += (FS)->cache_hits; + cmiss_tot += (FS)->cache_misses; + printf(" cache hits : %i (sum %i)\n", (FS)->cache_hits, chits_tot); + printf(" cache misses : %i (sum %i)\n", (FS)->cache_misses, cmiss_tot); + printf(" cache utiliz : %f\n", ((float)chits_tot/(float)(chits_tot + cmiss_tot))); +#endif +#endif + + +} + +typedef enum { + EMPTY, + SMALL, + MEDIUM, + LARGE, +} tfile_size; + +typedef enum { + UNTAMPERED, + APPENDED, + MODIFIED, + REWRITTEN, +} tfile_type; + +typedef enum { + SHORT = 4, + NORMAL = 20, + LONG = 100, +} tfile_life; + +typedef struct { + tfile_size tsize; + tfile_type ttype; + tfile_life tlife; +} tfile_conf; + +typedef struct { + int state; + spiffs_file fd; + tfile_conf cfg; + char name[32]; +} tfile; + +u32_t tfile_get_size(tfile_size s) { + switch (s) { + case EMPTY: + return 0; + case SMALL: + return SPIFFS_DATA_PAGE_SIZE(FS)/2; + case MEDIUM: + return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); + case LARGE: + return (FS)->cfg.phys_size/3; + } + return 0; +} + +int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files) { + int res; + tfile *tfiles = malloc(sizeof(tfile) * max_concurrent_files); + memset(tfiles, 0, sizeof(tfile) * max_concurrent_files); + int run = 0; + int cur_config_ix = 0; + char name[32]; + while (run < max_runs) { + printf(" run %i/%i\n", run, max_runs); + int i; + for (i = 0; i < max_concurrent_files; i++) { + sprintf(name, "file%i_%i", (1+run), i); + tfile *tf = &tfiles[i]; + if (tf->state == 0 && cur_config_ix < cfg_count) { +// create a new file + strcpy(tf->name, name); + printf(" create new %s with cfg %i/%i\n", name, (1+cur_config_ix), cfg_count); + tf->state = 1; + tf->cfg = cfgs[cur_config_ix]; + if (tf->cfg.tsize == EMPTY) { + res = SPIFFS_creat(FS, name, 0); + CHECK_RES(res); + int pfd = open(make_test_fname(name), O_TRUNC | O_CREAT | O_RDWR); + close(pfd); + int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; + spiffs_file fd = SPIFFS_open(FS, name, 0, extra_flags | SPIFFS_RDWR); + CHECK(fd > 0); + tf->fd = fd; + } else { + int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; + spiffs_file fd = SPIFFS_open(FS, name, 0, extra_flags | SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + CHECK(fd > 0); + extra_flags = tf->cfg.ttype == APPENDED ? O_APPEND : 0; + int pfd = open(make_test_fname(name), extra_flags | O_TRUNC | O_CREAT | O_RDWR); + tf->fd = fd; + int size = tfile_get_size(tf->cfg.tsize); + u8_t *buf = malloc(size); + memrand(buf, size); + res = SPIFFS_write(FS, fd, buf, size); + CHECK_RES(res); + write(pfd, buf, size); + close(pfd); + free(buf); + res = read_and_verify(name); + CHECK_RES(res); + } + + cur_config_ix++; + } else if (tf->state > 0) { +// hande file lifecycle + switch (tf->cfg.ttype) { + case UNTAMPERED: { + break; + } + case APPENDED: { + printf(" appending %s\n", tf->name); + int size = SPIFFS_DATA_PAGE_SIZE(FS)*3; + u8_t *buf = malloc(size); + memrand(buf, size); + res = SPIFFS_write(FS, tf->fd, buf, size); + CHECK_RES(res); + int pfd = open(make_test_fname(tf->name), O_APPEND | O_RDWR); + write(pfd, buf, size); + close(pfd); + free(buf); + res = read_and_verify(tf->name); + CHECK_RES(res); + break; + } + case MODIFIED: { + printf(" modify %s\n", tf->name); + spiffs_stat stat; + res = SPIFFS_fstat(FS, tf->fd, &stat); + CHECK_RES(res); + int size = stat.size / tf->cfg.tlife + SPIFFS_DATA_PAGE_SIZE(FS)/3; + int offs = (stat.size / tf->cfg.tlife) * tf->state; + res = SPIFFS_lseek(FS, tf->fd, offs, SPIFFS_SEEK_SET); + CHECK_RES(res); + u8_t *buf = malloc(size); + memrand(buf, size); + res = SPIFFS_write(FS, tf->fd, buf, size); + CHECK_RES(res); + int pfd = open(make_test_fname(tf->name), O_RDWR); + lseek(pfd, offs, SEEK_SET); + write(pfd, buf, size); + close(pfd); + free(buf); + res = read_and_verify(tf->name); + CHECK_RES(res); + break; + } + case REWRITTEN: { + if (tf->fd > 0) { + SPIFFS_close(FS, tf->fd); + } + printf(" rewriting %s\n", tf->name); + spiffs_file fd = SPIFFS_open(FS, tf->name, 0, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + CHECK(fd > 0); + int pfd = open(make_test_fname(tf->name), O_TRUNC | O_CREAT | O_RDWR); + tf->fd = fd; + int size = tfile_get_size(tf->cfg.tsize); + u8_t *buf = malloc(size); + memrand(buf, size); + res = SPIFFS_write(FS, fd, buf, size); + CHECK_RES(res); + write(pfd, buf, size); + close(pfd); + free(buf); + res = read_and_verify(tf->name); + CHECK_RES(res); + break; + } + } + tf->state++; + if (tf->state > tf->cfg.tlife) { +// file outlived its time, kill it + if (tf->fd > 0) { + SPIFFS_close(FS, tf->fd); + } + printf(" removing %s\n", tf->name); + res = read_and_verify(tf->name); + CHECK_RES(res); + res = SPIFFS_remove(FS, tf->name); + CHECK_RES(res); + remove(make_test_fname(tf->name)); + memset(tf, 0, sizeof(tf)); + } + + } + } + + run++; + } + free(tfiles); + return 0; +} + + + + +SUITE(hydrogen_tests) +void setup() { + _setup(); +} +void teardown() { + _teardown(); +} + + +TEST(missing_file) +{ + spiffs_file fd = SPIFFS_open(FS, "this_wont_exist", 0, 0); + TEST_CHECK(fd < 0); + return TEST_RES_OK; +} +TEST_END(missing_file) + + +TEST(bad_fd) +{ + int res; + spiffs_stat s; + spiffs_file fd = SPIFFS_open(FS, "this_wont_exist", 0, 0); + TEST_CHECK(fd < 0); + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR); + res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR); + res = SPIFFS_read(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR); + res = SPIFFS_write(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR); + return TEST_RES_OK; +} +TEST_END(bad_fd) + + +TEST(closed_fd) +{ + int res; + spiffs_stat s; + res = test_create_file("file"); + spiffs_file fd = SPIFFS_open(FS, "file", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + SPIFFS_close(FS, fd); + + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_read(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_write(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + return TEST_RES_OK; +} +TEST_END(closed_fd) + + +TEST(deleted_same_fd) +{ + int res; + spiffs_stat s; + spiffs_file fd; + res = test_create_file("remove"); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res >= 0); + + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_read(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_write(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + + return TEST_RES_OK; +} +TEST_END(deleted_same_fd) + + +TEST(deleted_other_fd) +{ + int res; + spiffs_stat s; + spiffs_file fd, fd_orig; + res = test_create_file("remove"); + fd_orig = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd_orig >= 0); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_fremove(FS, fd_orig); + TEST_CHECK(res >= 0); + SPIFFS_close(FS, fd_orig); + + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_read(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + res = SPIFFS_write(FS, fd, 0, 0); + TEST_CHECK(res < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED); + + return TEST_RES_OK; +} +TEST_END(deleted_other_fd) + + +TEST(file_by_open) +{ + int res; + spiffs_stat s; + spiffs_file fd = SPIFFS_open(FS, "filebopen", 0, SPIFFS_CREAT); + TEST_CHECK(fd >= 0); + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res >= 0); + TEST_CHECK(strcmp((char*)s.name, "filebopen") == 0); + TEST_CHECK(s.size == 0); + SPIFFS_close(FS, fd); + + fd = SPIFFS_open(FS, "filebopen", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_fstat(FS, fd, &s); + TEST_CHECK(res >= 0); + TEST_CHECK(strcmp((char*)s.name, "filebopen") == 0); + TEST_CHECK(s.size == 0); + SPIFFS_close(FS, fd); + return TEST_RES_OK; +} +TEST_END(file_by_open) + + +TEST(file_by_creat) +{ + int res; + res = test_create_file("filebcreat"); + TEST_CHECK(res >= 0); + return TEST_RES_OK; +} +TEST_END(file_by_creat) + + + + +TEST(list_dir) +{ + int res; + res = test_create_file("file1"); + TEST_CHECK(res >= 0); + res = test_create_file("file2"); + TEST_CHECK(res >= 0); + res = test_create_file("file3"); + TEST_CHECK(res >= 0); + res = test_create_file("file4"); + TEST_CHECK(res >= 0); + + spiffs_DIR d; + struct spiffs_dirent e; + struct spiffs_dirent *pe = &e; + + SPIFFS_opendir(FS, "/", &d); + while ((pe = SPIFFS_readdir(&d, pe))) { + printf(" %s\n", pe->name); + // TODO verify + } + SPIFFS_closedir(&d); + + return TEST_RES_OK; +} +TEST_END(list_dir) + + + +TEST(remove_single_by_path) +{ + int res; + spiffs_file fd; + res = test_create_file("remove"); + TEST_CHECK(res >= 0); + res = SPIFFS_remove(FS, "remove"); + TEST_CHECK(res >= 0); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND); + + return TEST_RES_OK; +} +TEST_END(remove_single_by_path) + + +TEST(remove_single_by_fd) +{ + int res; + spiffs_file fd; + res = test_create_file("remove"); + TEST_CHECK(res >= 0); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res >= 0); + SPIFFS_close(FS, fd); + fd = SPIFFS_open(FS, "remove", 0, SPIFFS_RDONLY); + TEST_CHECK(fd < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND); + + return TEST_RES_OK; +} +TEST_END(remove_single_by_fd) + + +TEST(write_big_file_chunks_page) +{ + int size = ((50*(FS)->cfg.phys_size)/100); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, SPIFFS_DATA_PAGE_SIZE(FS)); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_big_file_chunks_page) + + +TEST(write_big_files_chunks_page) +{ + char name[32]; + int f; + int files = 10; + int res; + int size = ((50*(FS)->cfg.phys_size)/100)/files; + printf(" filesize %i\n", size); + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = test_create_and_write_file(name, size, SPIFFS_DATA_PAGE_SIZE(FS)); + TEST_CHECK(res >= 0); + } + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = read_and_verify(name); + TEST_CHECK(res >= 0); + } + + return TEST_RES_OK; +} +TEST_END(write_big_files_chunks_page) + + +TEST(write_big_file_chunks_index) +{ + int size = ((50*(FS)->cfg.phys_size)/100); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, SPIFFS_DATA_PAGE_SIZE(FS) * SPIFFS_OBJ_HDR_IX_LEN(FS)); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_big_file_chunks_index) + + +TEST(write_big_files_chunks_index) +{ + char name[32]; + int f; + int files = 10; + int res; + int size = ((50*(FS)->cfg.phys_size)/100)/files; + printf(" filesize %i\n", size); + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = test_create_and_write_file(name, size, SPIFFS_DATA_PAGE_SIZE(FS) * SPIFFS_OBJ_HDR_IX_LEN(FS)); + TEST_CHECK(res >= 0); + } + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = read_and_verify(name); + TEST_CHECK(res >= 0); + } + + return TEST_RES_OK; +} +TEST_END(write_big_files_chunks_index) + + +TEST(write_big_file_chunks_huge) +{ + int size = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, size); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_big_file_chunks_huge) + + +TEST(write_big_files_chunks_huge) +{ + char name[32]; + int f; + int files = 10; + int res; + int size = ((50*(FS)->cfg.phys_size)/100)/files; + printf(" filesize %i\n", size); + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = test_create_and_write_file(name, size, size); + TEST_CHECK(res >= 0); + } + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = read_and_verify(name); + TEST_CHECK(res >= 0); + } + + return TEST_RES_OK; +} +TEST_END(write_big_files_chunks_huge) + + +TEST(truncate_big_file) +{ + int size = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, size); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + spiffs_file fd = SPIFFS_open(FS, "bigfile", 0, SPIFFS_RDWR); + TEST_CHECK(fd > 0); + res = SPIFFS_fremove(FS, fd); + TEST_CHECK(res >= 0); + SPIFFS_close(FS, fd); + + fd = SPIFFS_open(FS, "bigfile", 0, SPIFFS_RDWR); + TEST_CHECK(fd < 0); + TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND); + + return TEST_RES_OK; +} +TEST_END(truncate_big_file) + + + +TEST(file_uniqueness) +{ + int res; + spiffs_file fd; + char fname[32]; + int files = (FS_PURE_DATA_PAGES(FS) / 2) - SPIFFS_PAGES_PER_BLOCK(FS)*3; + int i; + printf(" creating %i files\n", files); + for (i = 0; i < files; i++) { + char content[20]; + sprintf(fname, "file%i", i); + sprintf(content, "%i", i); + res = test_create_file(fname); + TEST_CHECK(res >= 0); + fd = SPIFFS_open(FS, fname, 0, SPIFFS_APPEND | SPIFFS_RDWR); + TEST_CHECK(fd >= 0); + res = SPIFFS_write(FS, fd, content, strlen(content)+1); + TEST_CHECK(res >= 0); + SPIFFS_close(FS, fd); + } + printf(" checking %i files\n", files); + for (i = 0; i < files; i++) { + char content[20]; + char ref_content[20]; + sprintf(fname, "file%i", i); + sprintf(content, "%i", i); + fd = SPIFFS_open(FS, fname, 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_read(FS, fd, ref_content, strlen(content)+1); + TEST_CHECK(res >= 0); + TEST_CHECK(strcmp(ref_content, content) == 0); + SPIFFS_close(FS, fd); + } + printf(" removing %i files\n", files/2); + for (i = 0; i < files; i += 2) { + sprintf(fname, "file%i", i); + res = SPIFFS_remove(FS, fname); + TEST_CHECK(res >= 0); + } + printf(" creating %i files\n", files/2); + for (i = 0; i < files; i += 2) { + char content[20]; + sprintf(fname, "file%i", i); + sprintf(content, "new%i", i); + res = test_create_file(fname); + TEST_CHECK(res >= 0); + fd = SPIFFS_open(FS, fname, 0, SPIFFS_APPEND | SPIFFS_RDWR); + TEST_CHECK(fd >= 0); + res = SPIFFS_write(FS, fd, content, strlen(content)+1); + TEST_CHECK(res >= 0); + SPIFFS_close(FS, fd); + } + printf(" checking %i files\n", files); + for (i = 0; i < files; i++) { + char content[20]; + char ref_content[20]; + sprintf(fname, "file%i", i); + if ((i & 1) == 0) { + sprintf(content, "new%i", i); + } else { + sprintf(content, "%i", i); + } + fd = SPIFFS_open(FS, fname, 0, SPIFFS_RDONLY); + TEST_CHECK(fd >= 0); + res = SPIFFS_read(FS, fd, ref_content, strlen(content)+1); + TEST_CHECK(res >= 0); + TEST_CHECK(strcmp(ref_content, content) == 0); + SPIFFS_close(FS, fd); + } + + return TEST_RES_OK; +} +TEST_END(file_uniqueness) + +int create_and_read_back(int size, int chunk) { + char *name = "file"; + spiffs_file fd; + s32_t res; + + u8_t *buf = malloc(size); + memrand(buf, size); + + res = test_create_file(name); + CHECK(res >= 0); + fd = SPIFFS_open(FS, name, 0, SPIFFS_APPEND | SPIFFS_RDWR); + CHECK(fd >= 0); + res = SPIFFS_write(FS, fd, buf, size); + CHECK(res >= 0); + + spiffs_stat stat; + res = SPIFFS_fstat(FS, fd, &stat); + CHECK(res >= 0); + CHECK(stat.size == size); + + SPIFFS_close(FS, fd); + + fd = SPIFFS_open(FS, name, 0, SPIFFS_RDONLY); + CHECK(fd >= 0); + + u8_t *rbuf = malloc(size); + int offs = 0; + while (offs < size) { + int len = MIN(size - offs, chunk); + res = SPIFFS_read(FS, fd, &rbuf[offs], len); + CHECK(res >= 0); + CHECK(memcmp(&rbuf[offs], &buf[offs], len) == 0); + + offs += chunk; + } + + CHECK(memcmp(&rbuf[0], &buf[0], size) == 0); + + SPIFFS_close(FS, fd); + + free(rbuf); + free(buf); + + return 0; +} + +TEST(read_chunk_1) +{ + TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*8, 1) == 0); + return TEST_RES_OK; +} +TEST_END(read_chunk_1) + + +TEST(read_chunk_page) +{ + TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))*2, + SPIFFS_DATA_PAGE_SIZE(FS)) == 0); + return TEST_RES_OK; +} +TEST_END(read_chunk_page) + + +TEST(read_chunk_index) +{ + TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))*4, + SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))) == 0); + return TEST_RES_OK; +} +TEST_END(read_chunk_index) + + +TEST(read_chunk_huge) +{ + int sz = (2*(FS)->cfg.phys_size)/3; + TEST_CHECK(create_and_read_back(sz, sz) == 0); + return TEST_RES_OK; +} +TEST_END(read_chunk_huge) + + +TEST(lseek_simple_modification) { + int res; + spiffs_file fd; + char *fname = "seekfile"; + int i; + int len = 4096; + fd = SPIFFS_open(FS, fname, 0, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + TEST_CHECK(fd > 0); + int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR); + u8_t *buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + + res = SPIFFS_lseek(FS, fd, len/2, SPIFFS_SEEK_SET); + TEST_CHECK(res >= 0); + lseek(pfd, len/2, SEEK_SET); + len = len/4; + buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + + SPIFFS_close(FS, fd); + close(pfd); + + return TEST_RES_OK; +} +TEST_END(lseek_simple_modification) + + +TEST(lseek_modification_append) { + int res; + spiffs_file fd; + char *fname = "seekfile"; + int i; + int len = 4096; + fd = SPIFFS_open(FS, fname, 0, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + TEST_CHECK(fd > 0); + int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR); + u8_t *buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + + res = SPIFFS_lseek(FS, fd, len/2, SPIFFS_SEEK_SET); + TEST_CHECK(res >= 0); + lseek(pfd, len/2, SEEK_SET); + + buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + + SPIFFS_close(FS, fd); + close(pfd); + + return TEST_RES_OK; +} +TEST_END(lseek_modification_append) + + +TEST(lseek_modification_append_multi) { + int res; + spiffs_file fd; + char *fname = "seekfile"; + int len = 1024; + int runs = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS) / (len/2); + + fd = SPIFFS_open(FS, fname, 0, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + TEST_CHECK(fd > 0); + int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR); + u8_t *buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + + while (runs--) { + res = SPIFFS_lseek(FS, fd, -len/2, SPIFFS_SEEK_END); + TEST_CHECK(res >= 0); + lseek(pfd, -len/2, SEEK_END); + + buf = malloc(len); + memrand(buf, len); + res = SPIFFS_write(FS, fd, buf, len); + TEST_CHECK(res >= 0); + write(pfd, buf, len); + free(buf); + + res = read_and_verify(fname); + TEST_CHECK(res >= 0); + } + + SPIFFS_close(FS, fd); + close(pfd); + + return TEST_RES_OK; +} +TEST_END(lseek_modification_append_multi) + + +TEST(lseek_read) { + int res; + spiffs_file fd; + char *fname = "seekfile"; + int len = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS); + int runs = 100000; + + fd = SPIFFS_open(FS, fname, 0, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR); + TEST_CHECK(fd > 0); + u8_t *refbuf = malloc(len); + memrand(refbuf, len); + res = SPIFFS_write(FS, fd, refbuf, len); + TEST_CHECK(res >= 0); + + int offs = 0; + res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_SET); + TEST_CHECK(res >= 0); + + while (runs--) { + int i; + u8_t buf[64]; + if (offs + 41 + sizeof(buf) >= len) { + offs = (offs + 41 + sizeof(buf)) % len; + res = SPIFFS_lseek(FS, fd, offs, SPIFFS_SEEK_SET); + TEST_CHECK(res >= 0); + } + res = SPIFFS_lseek(FS, fd, 41, SPIFFS_SEEK_CUR); + TEST_CHECK(res >= 0); + offs += 41; + res = SPIFFS_read(FS, fd, buf, sizeof(buf)); + TEST_CHECK(res >= 0); + for (i = 0; i < sizeof(buf); i++) { + if (buf[i] != refbuf[offs+i]) { + printf(" mismatch at offs %i\n", offs); + } + TEST_CHECK(buf[i] == refbuf[offs+i]); + } + offs += sizeof(buf); + + res = SPIFFS_lseek(FS, fd, -(sizeof(buf)+11), SPIFFS_SEEK_CUR); + TEST_CHECK(res >= 0); + offs -= (sizeof(buf)+11); + res = SPIFFS_read(FS, fd, buf, sizeof(buf)); + TEST_CHECK(res >= 0); + for (i = 0; i < sizeof(buf); i++) { + if (buf[i] != refbuf[offs+i]) { + printf(" mismatch at offs %i\n", offs); + } + TEST_CHECK(buf[i] == refbuf[offs+i]); + } + offs += sizeof(buf); + } + + free(refbuf); + SPIFFS_close(FS, fd); + + return TEST_RES_OK; +} +TEST_END(lseek_read) + + +#ifndef REMOVE_LONG_TESTS +TEST(write_small_file_chunks_1) +{ + int res = test_create_and_write_file("smallfile", 256, 1); + TEST_CHECK(res >= 0); + res = read_and_verify("smallfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_small_file_chunks_1) + + +TEST(write_small_files_chunks_1) +{ + char name[32]; + int f; + int size = 512; + int files = ((20*(FS)->cfg.phys_size)/100)/size; + int res; + for (f = 0; f < files; f++) { + sprintf(name, "smallfile%i", f); + res = test_create_and_write_file(name, size, 1); + TEST_CHECK(res >= 0); + } + for (f = 0; f < files; f++) { + sprintf(name, "smallfile%i", f); + res = read_and_verify(name); + TEST_CHECK(res >= 0); + } + + return TEST_RES_OK; +} +TEST_END(write_small_files_chunks_1) + +TEST(write_big_file_chunks_1) +{ + int size = ((50*(FS)->cfg.phys_size)/100); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, 1); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_big_file_chunks_1) + +TEST(write_big_files_chunks_1) +{ + char name[32]; + int f; + int files = 10; + int res; + int size = ((50*(FS)->cfg.phys_size)/100)/files; + printf(" filesize %i\n", size); + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = test_create_and_write_file(name, size, 1); + TEST_CHECK(res >= 0); + } + for (f = 0; f < files; f++) { + sprintf(name, "bigfile%i", f); + res = read_and_verify(name); + TEST_CHECK(res >= 0); + } + + return TEST_RES_OK; +} +TEST_END(write_big_files_chunks_1) +#endif // REMOVE_LONG_TESTS + +TEST(long_run_config_many_small_one_long) +{ + tfile_conf cfgs[] = { + { .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = LONG + }, + }; + + int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 206, 5); + TEST_CHECK(res >= 0); + return TEST_RES_OK; +} +TEST_END(long_run_config_many_small_one_long) + +TEST(long_run_config_many_medium) +{ + tfile_conf cfgs[] = { + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG + }, + }; + + int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 305, 5); + TEST_CHECK(res >= 0); + return TEST_RES_OK; +} +TEST_END(long_run_config_many_medium) + + +TEST(long_run_config_many_small) +{ + tfile_conf cfgs[] = { + { .tsize = SMALL, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + }; + + int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 115, 6); + TEST_CHECK(res >= 0); + return TEST_RES_OK; +} +TEST_END(long_run_config_many_small) + +SUITE_END(hydrogen_tests) + +SUITE(dev_tests) +void setup() { + _setup(); +} +void teardown() { + _teardown(); +} +#if 0 +TEST(write_small_file_chunks_1) +{ + int res = test_create_and_write_file("smallfile", 600, 1); + TEST_CHECK(res >= 0); + res = read_and_verify("smallfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_small_file_chunks_1) + +TEST(write_big_file_chunks_page) +{ + int size = ((50*(FS)->cfg.phys_size)/100); + printf(" filesize %i\n", size); + int res = test_create_and_write_file("bigfile", size, SPIFFS_DATA_PAGE_SIZE(FS)); + TEST_CHECK(res >= 0); + res = read_and_verify("bigfile"); + TEST_CHECK(res >= 0); + + return TEST_RES_OK; +} +TEST_END(write_big_file_chunks_page) +#else + + +TEST(long_run_config_many_small) +{ + tfile_conf cfgs[] = { + { .tsize = SMALL, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = LONG + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM + }, + { .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT + }, + }; + + int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 115, 6); + TEST_CHECK(res >= 0); + return TEST_RES_OK; +} +TEST_END(long_run_config_many_small) +#endif +SUITE_END(dev_tests) + diff --git a/src/test_spiffs.c b/src/test_spiffs.c new file mode 100644 index 0000000..979a25b --- /dev/null +++ b/src/test_spiffs.c @@ -0,0 +1,270 @@ +/* + * test_spiffs.c + * + * Created on: Jun 19, 2013 + * Author: petera + */ + + +#include +#include +#include + +#include "stypes.h" +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#include "testrunner.h" + +#include "test_spiffs.h" + +#include +#include +#include +#include +#include + +#define SECTOR_SIZE 65536 //(0x10000) +#define PAGE_SIZE 256 //256 +#define LOG_BLOCK 65536 +#define LOG_PAGE PAGE_SIZE + +static unsigned char area[1024*1024*1]; + +static int erases[sizeof(area)/SECTOR_SIZE]; +static char _path[256]; + +spiffs __fs; +static u8_t _work[LOG_PAGE*2]; +static u8_t _fds[256+128]; +static u8_t _cache[256*5]; + +#define TEST_PATH "/home/petera/proj/spiffs_test_/test_data/" + +char *make_test_fname(const char *name) { + sprintf(_path, "%s%s", TEST_PATH, name); + return _path; +} + +void clear_test_path() { + DIR *dp; + struct dirent *ep; + dp = opendir(TEST_PATH); + + if (dp != NULL) { + while ((ep = readdir(dp))) { + if (ep->d_name[0] != '.') { + sprintf(_path, "%s%s", TEST_PATH, ep->d_name); + remove(_path); + } + } + closedir(dp); + } +} + +static s32_t _read(u32_t addr, u32_t size, u8_t *dst) { + memcpy(dst, &area[addr], size); + return 0; +} + +static s32_t _write(u32_t addr, u32_t size, u8_t *src) { + int i; + //printf("wr %08x %i\n", addr, size); + for (i = 0; i < size; i++) { + if (((addr + i) & (PAGE_SIZE-1)) != offsetof(spiffs_page_header, flags)) { + if (((area[addr + i] ^ src[i]) & src[i])) { + printf("trying to write %02x to %02x at addr %08x\n", src[i], area[addr + i], addr+i); + spiffs_page_ix pix = (addr + i) / PAGE_SIZE; + dump_page(&__fs, pix); + return -1; + } + } + area[addr + i] &= src[i]; + } + return 0; +} + +static s32_t _erase(u32_t addr, u32_t size) { + if (addr & (SECTOR_SIZE-1)) { + return -1; + } + if (size & (SECTOR_SIZE-1)) { + return -1; + } + erases[addr/SECTOR_SIZE]++; + memset(&area[addr], 0xff, size); + return 0; +} + +void hexdump_mem(u8_t *b, u32_t len) { + while (len--) { + if ((((u32_t)b)&0x1f) == 0) { + printf("\n"); + } + printf("%02x", *b++); + } + printf("\n"); +} + +void hexdump(u32_t addr, u32_t len) { + int remainder = (addr % 32) == 0 ? 0 : 32 - (addr % 32); + u32_t a; + for (a = addr - remainder; a < addr+len; a++) { + if ((a & 0x1f) == 0) { + if (a != addr) { + printf(" "); + int j; + for (j = 0; j < 32; j++) { + if (a-32+j < addr) + printf(" "); + else { + printf("%c", (area[a-32+j] < 32 || area[a-32+j] >= 0x7f) ? '.' : area[a-32+j]); + } + } + } + printf("%s %08x: ", a<=addr ? "":"\n", a); + } + if (a < addr) { + printf(" "); + } else { + printf("%02x", area[a]); + } + } + int j; + printf(" "); + for (j = 0; j < 32; j++) { + if (a-32+j < addr) + printf(" "); + else { + printf("%c", (area[a-32+j] < 32 || area[a-32+j] >= 0x7f) ? '.' : area[a-32+j]); + } + } + printf("\n"); +} + +void dump_page(spiffs *fs, spiffs_page_ix p) { + printf("page %04x ", p); + u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, p); + if (p % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // obj lu page + printf("OBJ_LU"); + } else { + // data page + spiffs_page_header *ph = (spiffs_page_header *)&area[addr]; + printf("DATA %04x:%04x ", ph->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, ph->span_ix); + printf("%s", ((ph->flags & SPIFFS_PH_FLAG_FINAL) == 0) ? "F" : "f"); + printf("%s", ((ph->flags & SPIFFS_PH_FLAG_DELET) == 0) ? "D" : "d"); + printf("%s", ((ph->flags & SPIFFS_PH_FLAG_INDEX) == 0) ? "I" : "i"); + printf("%s ", ((ph->flags & SPIFFS_PH_FLAG_CORRU) == 0) ? "C" : "c"); + if (ph->obj_id & SPIFFS_OBJ_ID_IX_FLAG) { + // object index + printf("OBJ_IX"); + if (ph->span_ix == 0) { + printf("_HDR "); + spiffs_page_object_ix_header *oix_hdr = (spiffs_page_object_ix_header *)&area[addr]; + printf("'%s' %i bytes type:%02x", oix_hdr->name, oix_hdr->size, oix_hdr->type); + } + } else { + // data page + printf("CONTENT"); + } + } + printf("\n"); + u32_t len = fs->cfg.log_page_size; + hexdump(addr, len); +} + +int read_and_verify(char *name) { + s32_t res; + int fd = SPIFFS_open(&__fs, name, 0, 0); + if (fd < 0) { + printf(" read_and_verify: could not open file %s\n", name); + return fd; + } + int pfd = open(make_test_fname(name), O_RDONLY); + spiffs_stat s; + res = SPIFFS_fstat(&__fs, fd, &s); + if (res < 0) { + printf(" read_and_verify: could not stat file %s\n", name); + return res; + } + if (s.size == 0) { + SPIFFS_close(&__fs, fd); + close(pfd); + return 0; + } + + //printf("verifying %s, len %i\n", name, s.size); + int offs = 0; + u8_t buf_d[256]; + u8_t buf_v[256]; + while (offs < s.size) { + int read_len = MIN(s.size - offs, sizeof(buf_d)); + res = SPIFFS_read(&__fs, fd, buf_d, read_len); + if (res < 0) { + printf(" read_and_verify: could not read file %s offs:%i len:%i filelen:%i\n", name, offs, read_len, s.size); + return res; + } + int pres = read(pfd, buf_v, read_len); + (void)pres; + //printf("reading offs:%i len:%i spiffs_res:%i posix_res:%i\n", offs, read_len, res, pres); + int i; + int veri_ok = 1; + for (i = 0; veri_ok && i < read_len; i++) { + if (buf_d[i] != buf_v[i]) { + printf("file verification mismatch @ %i, %02x %c != %02x %c\n", offs+i, buf_d[i], buf_d[i], buf_v[i], buf_v[i]); + int j = MAX(0, i-16); + int k = MIN(sizeof(buf_d), i+16); + k = MIN(s.size-offs, k); + int l; + for (l = j; l < k; l++) { + printf("%c", buf_d[l] > 31 ? buf_d[l] : '.'); + } + printf("\n"); + for (l = j; l < k; l++) { + printf("%c", buf_v[l] > 31 ? buf_v[l] : '.'); + } + printf("\n"); + veri_ok = 0; + } + } + if (!veri_ok) { + SPIFFS_close(&__fs, fd); + close(pfd); + printf("data mismatch\n"); + return -1; + } + + offs += read_len; + } + + SPIFFS_close(&__fs, fd); + close(pfd); + + return 0; +} + +void fs_reset() { + memset(area, 0xff, sizeof(area)); + spiffs_config c; + c.hal_erase_f = _erase; + c.hal_read_f = _read; + c.hal_write_f = _write; + c.log_block_size = LOG_BLOCK; + c.log_page_size = LOG_PAGE; + c.phys_addr = 0; + c.phys_erase_block = SECTOR_SIZE; + c.phys_size = sizeof(area); + memset(erases,0,sizeof(erases)); + memset(_cache,0,sizeof(_cache)); + SPIFFS_init(&__fs, &c, _work, _fds, sizeof(_fds), _cache, sizeof(_cache)); +} + +void real_assert(int c, const char *n, const char *file, int l) { + if (c == 0) { + printf("ASSERT: %s %s @ %i\n", (n ? n : ""), file, l); + printf("fs errno:%i\n", __fs.errno); + exit(0); + } +} + diff --git a/src/test_spiffs.h b/src/test_spiffs.h new file mode 100644 index 0000000..cddb293 --- /dev/null +++ b/src/test_spiffs.h @@ -0,0 +1,25 @@ +/* + * test_spiffs.h + * + * Created on: Jun 19, 2013 + * Author: petera + */ + +#ifndef TEST_SPIFFS_H_ +#define TEST_SPIFFS_H_ + +#include "spiffs.h" + +#define FS &__fs + +extern spiffs __fs; + +void fs_reset(); +int read_and_verify(char *name); +void dump_page(spiffs *fs, spiffs_page_ix p); +void hexdump(u32_t addr, u32_t len); +char *make_test_fname(const char *name); +void clear_test_path(); + + +#endif /* TEST_SPIFFS_H_ */ diff --git a/src/testrunner.c b/src/testrunner.c new file mode 100644 index 0000000..633105b --- /dev/null +++ b/src/testrunner.c @@ -0,0 +1,120 @@ +/* + * testrunner.c + * + * Created on: Jun 18, 2013 + * Author: petera + */ + + +#include +#include +#include +#include "testrunner.h" + +static struct { + test *tests; + test *_last_test; + int test_count; + void (*on_stop)(test *t); + test_res *failed; + test_res *failed_last; + test_res *stopped; + test_res *stopped_last; +} test_main; + +void test_init(void (*on_stop)(test *t)) { + test_main.on_stop = on_stop; +} + +void add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t)) { + if (f == 0) return; + DBGT("adding test %s\n", name); + test *t = malloc(sizeof(test)); + memset(t, 0, sizeof(test)); + t->f = f; + strcpy(t->name, name); + t->setup = setup; + t->teardown = teardown; + if (test_main.tests == 0) { + test_main.tests = t; + } else { + test_main._last_test->_next = t; + } + test_main._last_test = t; + test_main.test_count++; +} + +static void add_res(test *t, test_res **head, test_res **last) { + test_res *tr = malloc(sizeof(test_res)); + memset(tr,0,sizeof(test_res)); + strcpy(tr->name, t->name); + if (*head == 0) { + *head = tr; + } else { + (*last)->_next = tr; + } + *last = tr; +} + +static void dump_res(test_res **head) { + test_res *tr = (*head); + while (tr) { + test_res *next_tr = tr->_next; + printf(" %s\n", tr->name); + free(tr); + tr = next_tr; + } +} + +void run_tests() { + memset(&test_main, 0, sizeof(test_main)); + DBGT("adding suites...\n"); + add_suites(); + DBGT("%i tests added\n", test_main.test_count); + DBGT("running tests...\n"); + int ok = 0; + int failed = 0; + int stopped = 0; + test *cur_t = test_main.tests; + int i = 1; + while (cur_t) { + cur_t->setup(cur_t); + test *next_test = cur_t->_next; + DBGT("TEST %i/%i : running test %s\n", i, test_main.test_count, cur_t->name); + i++; + int res = cur_t->f(cur_t); + cur_t->teardown(cur_t); + switch (res) { + case TEST_RES_OK: + ok++; + printf(" .. ok\n"); + break; + case TEST_RES_FAIL: + failed++; + printf(" .. FAILED\n"); + if (test_main.on_stop) test_main.on_stop(cur_t); + add_res(cur_t, &test_main.failed, &test_main.failed_last); + break; + case TEST_RES_ASSERT: + stopped++; + printf(" .. ABORTED\n"); + if (test_main.on_stop) test_main.on_stop(cur_t); + add_res(cur_t, &test_main.stopped, &test_main.stopped_last); + break; + } + free(cur_t); + cur_t = next_test; + } + DBGT("ran %i tests\n", test_main.test_count); + printf("Test report, %i tests\n", test_main.test_count); + printf("%i succeeded\n", ok); + printf("%i failed\n", failed); + dump_res(&test_main.failed); + printf("%i stopped\n", stopped); + dump_res(&test_main.stopped); + if (ok < test_main.test_count) { + printf("\nFAILED\n"); + } else { + printf("\nALL TESTS OK\n"); + } +} diff --git a/src/testrunner.h b/src/testrunner.h new file mode 100644 index 0000000..72d0350 --- /dev/null +++ b/src/testrunner.h @@ -0,0 +1,110 @@ +/* + * testrunner.h + * + * Created on: Jun 19, 2013 + * Author: petera + */ + +/* + +SUITE(mysuite) + +void setup(test *t) {} + +void teardown(test *t) {} + +TEST(mytest) { + printf("mytest runs now..\n"); + return 0; +} TEST_END(mytest) + +SUITE_END(mysuite) + + + +SUITE(mysuite2) + +void setup(test *t) {} + +void teardown(test *t) {} + +TEST(mytest2a) { + printf("mytest2a runs now..\n"); + return 0; +} TEST_END(mytest2a) + +TEST(mytest2b) { + printf("mytest2b runs now..\n"); + return 0; +} TEST_END(mytest2b) + +SUITE_END(mysuite2) + + + +void add_suites() { + ADD_SUITE(mysuite); + ADD_SUITE(mysuite2); +} + */ + +#ifndef TESTS_H_ +#define TESTS_H_ + +#define TEST_RES_OK 0 +#define TEST_RES_FAIL -1 +#define TEST_RES_ASSERT -2 + +struct test_s; + +typedef int (*test_f)(struct test_s *t); + +typedef struct test_s { + test_f f; + char name[256]; + void *data; + void (*setup)(struct test_s *t); + void (*teardown)(struct test_s *t); + struct test_s *_next; +} test; + +typedef struct test_res_s { + char name[256]; + struct test_res_s *_next; +} test_res; + +#define TEST_CHECK(x) if (!(x)) { \ + printf(" TEST FAIL %s:%i\n", __FILE__, __LINE__); \ + goto __fail_stop; \ +} +#define TEST_ASSERT(x) if (!(x)) { \ + printf(" TEST ASSERT %s:%i\n", __FILE__, __LINE__); \ + goto __fail_assert; \ +} + +#define DBGT(...) printf(__VA_ARGS__) + +#define str(s) #s + +#define SUITE(sui) \ + extern void __suite_##sui() { +#define SUITE_END(sui) \ + } +#define ADD_SUITE(sui) \ + __suite_##sui(); +#define TEST(tf) \ + int tf(struct test_s *t) { do +#define TEST_END(tf) \ + while(0); \ + __fail_stop: return TEST_RES_FAIL; \ + __fail_assert: return TEST_RES_ASSERT; \ + } \ + add_test(tf, str(tf), setup, teardown); + + +void add_suites(); +void test_init(void (*on_stop)(test *t)); +void add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t)); +void run_tests(); + +#endif /* TESTS_H_ */ diff --git a/src/testsuites.c b/src/testsuites.c new file mode 100644 index 0000000..a7c51e7 --- /dev/null +++ b/src/testsuites.c @@ -0,0 +1,13 @@ +/* + * testsuites.c + * + * Created on: Jun 19, 2013 + * Author: petera + */ + +#include "testrunner.h" + +void add_suites() { + ADD_SUITE(hydrogen_tests) + //ADD_SUITE(dev_tests); +}