diff options
| author | Rickard Green <rickard@erlang.org> | 2023-03-05 18:43:13 +0100 |
|---|---|---|
| committer | Rickard Green <rickard@erlang.org> | 2023-03-15 13:05:24 +0100 |
| commit | e6074fada29f7ec8a8ffd28e6a3aab75f74199f5 (patch) | |
| tree | a882f19531a636db16ff1fec39fe3c99177bb0fe | |
| parent | 2e9f3f57dc6b3c7e4ce48a9955abf16e3dc6c16d (diff) | |
| download | erlang-e6074fada29f7ec8a8ffd28e6a3aab75f74199f5.tar.gz | |
[erts] Option 'local' of term_to_binary/2 and term_to_iovec/2
Introduce a local external term format with an unspecified encoding. This
external term format is used for supporting the 'local' option of
term_to_binary/2 and term_to_iovec/2. Terms encoded on this format is only
to be decoded by the same runtime system instance that encoded it.
22 files changed, 1615 insertions, 283 deletions
diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml index 2547b6e952..ef51504af4 100644 --- a/erts/doc/src/erl_driver.xml +++ b/erts/doc/src/erl_driver.xml @@ -2567,12 +2567,14 @@ ErlDrvTermData spec[] = { ERL_DRV_STRING_CONS, (ErlDrvTermData)"abc", 3, }; erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0])); ]]></code> - <p>The <c>ERL_DRV_EXT2TERM</c> term type is used for passing a + <p> + <marker id="ERL_DRV_EXT2TERM"/> + The <c>ERL_DRV_EXT2TERM</c> term type is used for passing a term encoded with the <seeguide marker="erl_ext_dist">external format</seeguide>, that is, a term that has been encoded by <seemfa marker="erlang#term_to_binary/2"> - <c>erlang:term_to_binary</c></seemfa>, + <c>erlang:term_to_binary()</c></seemfa>, <seecref marker="erl_interface:ei"><c>erl_interface:ei(3)</c></seecref>, and so on. For example, if <c>binp</c> is a pointer to an <c>ErlDrvBinary</c> diff --git a/erts/doc/src/erl_ext_dist.xml b/erts/doc/src/erl_ext_dist.xml index 09c3cfc5c7..712c30afc0 100644 --- a/erts/doc/src/erl_ext_dist.xml +++ b/erts/doc/src/erl_ext_dist.xml @@ -1421,6 +1421,46 @@ </note> </section> + <section> + <marker id="LOCAL_EXT"/> + <title>LOCAL_EXT</title> + <table align="left"> + <row> + <cell align="center">1</cell> + <cell align="center">...</cell> + </row> + <row> + <cell align="center"><c>121</c></cell> + <cell align="center">...</cell> + </row> + <tcaption>LOCAL_EXT</tcaption></table> + <p> + Marks that this is encoded on an alternative local external term + format intended to only be decoded by a specific local decoder. + The bytes following from here on may contain any unspecified type + of encoding of terms. It is the responsibility of the user to only + attempt to decode terms on the local external term format which has + been produced by a matching encoder. + </p> + <p> + This tag is used by the Erlang runtime system upon encoding the local + external term format when the + <seeerl marker="erts:erlang#term_to_binary_local"><c>local</c></seeerl> + option is passed to + <seemfa marker="erts:erlang#term_to_binary/2"><c>term_to_binary/2</c></seemfa>, + but can be used by other encoders as well providing similar + functionality. The Erlang runtime system adds a hash immediately + following the <c>LOCAL_EXT</c> tag which is verified on decoding in + order to verify that encoder and decoder match which might be a good + practice. This will very likely catch mistakes made by users, but + is not guaranteed to, and is not intended to, prevent decoding of an + intentionally forged encoding on the local external term format. + </p> + <p> + <c>LOCAL_EXT</c> was introduced in OTP @OTP-18477@. + </p> + </section> + </chapter> diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index ff963047ca..d0bd54ea73 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -3324,7 +3324,7 @@ first</pre> </list> <p>A node can also be alive if it has got a name from a call to <seemfa - marker="kernel:net_kernel#start/1"><c>net_kernel:start/1</c></seemfa> + marker="kernel:net_kernel#start/2"><c>net_kernel:start/2</c></seemfa> and has not been stopped by a call to <seemfa marker="kernel:net_kernel#stop/0"><c>net_kernel:stop/0</c></seemfa>.</p> </desc> @@ -11846,60 +11846,192 @@ hello <desc> <p>Returns a binary data object that is the result of encoding <c><anno>Term</anno></c> according to the Erlang external - term format.</p> - <p>If option <c>compressed</c> is provided, the external term - format is compressed. The compressed format is automatically - recognized by <c>binary_to_term/1</c> as from Erlang/OTP R7B.</p> - <p>A compression level can be specified by giving option - <c>{compressed, <anno>Level</anno>}</c>. - <c><anno>Level</anno></c> is an integer - with range 0..9, where:</p> - <list type="bulleted"> - <item><p><c>0</c> - No compression is done (it is the same as - giving no <c>compressed</c> option).</p></item> - <item><p><c>1</c> - Takes least time but may not compress - as well as the higher levels.</p></item> - <item><p><c>6</c> - Default level when option <c>compressed</c> - is provided.</p></item> - <item><p><c>9</c> - Takes most time and tries to produce a smaller - result. Notice "tries" in the preceding sentence; depending - on the input term, level 9 compression either does or does - not produce a smaller result than level 1 compression.</p></item> - </list> - <p>Option <c>{minor_version, <anno>Version</anno>}</c> - can be used to control some - encoding details. This option was introduced in Erlang/OTP R11B-4. - The valid values for <c><anno>Version</anno></c> are:</p> - <taglist> - <tag><c>0</c></tag> - <item> - <p>Floats are encoded using a textual representation.</p> - <p>Atoms that can be represented by a latin1 string are encoded - using latin1 while only atoms that cannot be represented by latin1 - are encoded using utf8.</p> - </item> - <tag><c>1</c></tag> - <item> - <p>Floats are encoded in a more space-efficient and exact way - (namely in the 64-bit IEEE format, rather than converted to a - textual representation). As from Erlang/OTP R11B-4, - <c>binary_to_term/1</c> can decode this representation.</p> - <p>Atoms that can be represented by a latin1 string are encoded - using latin1 while only atoms that cannot be represented by latin1 - are encoded using utf8.</p> - </item> - <tag><c>2</c></tag> - <item> - <p>This is as of Erlang/OTP 26.0 the <em>default</em>. Atoms are - unconditionally encoded using utf8. Erlang/OTP systems as of R16B - can decode this representation.</p> - </item> - </taglist> - <p>Option <c>deterministic</c> (introduced in OTP 24.1) can - be used to ensure that within the same major release of Erlang/OTP, - the same encoded representation is returned for the same - term. There is still no guarantee that the encoded representation - remains the same between major releases of Erlang/OTP.</p> + term format.</p> + <p>Currently supported options:</p> + <taglist> + <tag><c>compressed</c></tag> + <item> + <p> + Compress the external term format. The compressed + format is automatically recognized by <c>binary_to_term/1</c> + as from Erlang/OTP R7B. + </p> + </item> + <tag><c>{compressed, <anno>Level</anno>}</c></tag> + <item> + <p> + Compress the external term format to a given level. + The compression level is specified by <c><anno>Level</anno></c> + which is an integer in the range 0..9, where: + </p> + <taglist> + <tag><c>0</c></tag> + <item><p> + No compression is done (it is the same as giving no + <c>compressed</c> option). + </p></item> + <tag><c>1</c></tag> + <item><p> + Takes least time but may not compress as well as the higher + levels. + </p></item> + <tag><c>6</c></tag> + <item><p> + Default level when option <c>compressed</c> is provided. + </p></item> + <tag><c>9</c></tag> + <item><p> + Takes most time and tries to produce a smaller result. + Notice "tries" in the preceding sentence; depending + on the input term, level 9 compression either does or does + not produce a smaller result than level 1 compression. + </p></item> + </taglist> + </item> + <tag since="R11B-4"><c>{minor_version, <anno>Version</anno>}</c></tag> + <item> + <p> + The option can be used to control some encoding details. Valid + values for <c><anno>Version</anno></c> are: + </p> + <taglist> + <tag><c>0</c></tag> + <item> + <p>Floats are encoded using a textual representation.</p> + <p>Atoms that can be represented by a latin1 string are encoded + using latin1 while only atoms that cannot be represented by latin1 + are encoded using utf8.</p> + </item> + <tag><c>1</c></tag> + <item> + <p>Floats are encoded in a more space-efficient and exact way + (namely in the 64-bit IEEE format, rather than converted to a + textual representation). As from Erlang/OTP R11B-4, + <c>binary_to_term/1</c> can decode this representation.</p> + <p>Atoms that can be represented by a latin1 string are encoded + using latin1 while only atoms that cannot be represented by latin1 + are encoded using utf8.</p> + </item> + <tag><c>2</c></tag> + <item> + <p>This is as of Erlang/OTP 26.0 the <em>default</em>. Atoms are + unconditionally encoded using utf8. Erlang/OTP systems as of R16B + can decode this representation.</p> + </item> + </taglist> + </item> + <tag since="OTP 24.1"><c>deterministic</c></tag> + <item> + <p> + This option can be used to ensure that, within the same major + release of Erlang/OTP, the same encoded representation is returned + for the same term. There is still no guarantee that the encoded + representation remains the same between major releases of + Erlang/OTP. + </p> + <p> + This option cannot be combined with the <c>local</c> option. + </p> + </item> + <tag since="OTP @OTP-18477@"><c>local</c> + <marker id="term_to_binary_local"/></tag> + <item> + <p> + This option will cause encoding of <c><anno>Term</anno></c> + to an alternative local version of the external term format which + when decoded by the same runtime system instance will produce a + term identical to the encoded term even when the node name and/or + <seeerl marker="#system_info_creation">creation</seeerl> of the + current runtime system instance have changed between encoding + and decoding. When encoding without the <c>local</c> option, + local identifiers such as <seetype marker="#pid">pids</seetype>, + <seetype marker="#port">ports</seetype> and + <seetype marker="#reference">references</seetype> will not + be the same if node name and/or creation of the current runtime + system instance changed between encoding and decoding. This since + such identifiers refer to a specific node by node name and + creation. + </p> + <p> + Node name and creation of a runtime system instance change + when the distribution is started or stopped. The distribution is + started when the runtime system is started using the + <seecom marker="erl#name"><c>-name</c></seecom> or + <seecom marker="erl#sname"><c>-sname</c></seecom> command line + arguments. Note that the actual start of the distribution happens + after other code in the startup phase has begun executing. The + distribution can also be started by calling + <seemfa marker="kernel:net_kernel#start/2"><c>net_kernel:start/2</c></seemfa> + and stopped by calling + <seemfa marker="kernel:net_kernel#stop/0"><c>net_kernel:stop/1</c></seemfa> + if it has not been started via the command line. + </p> + <p> + The decoding of a term encoded with the <c>local</c> option, + using for example + <seemfa marker="#term_to_binary/1"><c>binary_to_term()</c></seemfa>, + will try to verify that the term actually was encoded by the same + runtime system instance, and will in the vast majority of cases + fail if the encoding was performed by another runtime system + instance. You should however <em>not</em> trust that this + verification will work in all cases. You <em>should</em> make + sure to <em>only</em> decode terms encoded with the <c>local</c> + option on the same Erlang runtime system instance as the one that + encoded the terms. + </p> + <p> + Since it is only the runtime system that encoded a term using + the <c>local</c> option that can decode it, the local encoding + is typically pieced together with something else to produce a + reply to where the <c>local</c> encoding originates from. + If a term encoded using the <c>local</c> option is stripped of + its leading version number, it can be added as part + of a larger term (for example as an element in a tuple) when + encoding on the external term format using, for example, + <seecref marker="erl_interface:ei">ei</seecref>. In the <c>ei</c> + case, you would strip it of the version number using + <seecref marker="erl_interface:ei#ei_decode_version"><c>ei_decode_version()</c></seecref> + and then add the remaining local encoding to what you are encoding + using for example + <seecref marker="erl_interface:ei#ei_x_append_buf"><c>ei_x_append_buf()</c></seecref>. + </p> + <p> + A good example of when you want to use the <c>local</c> option, is + when you want to make a request from a process to a port + <seecref marker="erl_driver">driver</seecref> and utilize the + <seeguide marker="system/efficiency_guide:processes#receiving-messages">selective + receive optimization</seeguide> when receiving the reply. In + this scenario you want to create a reference, serialize the + reference on the external term format using the <c>local</c> + option, pass this to the driver in the request, and then wait + for the reply message in a selective receive matching on the + reference. The driver should send the reply using either + <seecref marker="erl_driver#erl_drv_output_term"><c>erl_drv_output_term()</c></seecref> + or + <seecref marker="erl_driver#erl_drv_send_term"><c>erl_drv_send_term()</c></seecref> + using the term type + <seecref marker="erl_driver#ERL_DRV_EXT2TERM"><c>ERL_DRV_EXT2TERM</c></seecref> + for the, in the request, previously received reference on the + external term format. Note that you should not strip the + leading version number from the local encoding when using the + term type <c>ERL_DRV_EXT2TERM</c> of this functionality. If you + in this example do not encode the reference using the <c>local</c> + option, and the distribution is started or stopped while the + request is ongoing, the process that made the request will hang + indefinitely since the reference in the reply message will never + match. + </p> + <p> + This option cannot be combined with the <c>deterministic</c> + option. + </p> + <p> + For more information see the + <seeguide marker="erl_ext_dist#LOCAL_EXT"><c>LOCAL_EXT</c></seeguide> + tag in the documentation of the external term format. + </p> + </item> + </taglist> <p>See also <seemfa marker="#binary_to_term/1"> <c>binary_to_term/1</c></seemfa>.</p> </desc> diff --git a/erts/emulator/beam/atom.c b/erts/emulator/beam/atom.c index ffeeb664ad..a0e5c488b1 100644 --- a/erts/emulator/beam/atom.c +++ b/erts/emulator/beam/atom.c @@ -197,7 +197,6 @@ atom_alloc(Atom* tmpl) static void atom_free(Atom* obj) { - ASSERT(obj->slot.index == atom_val(am_ErtsSecretAtom)); } static void latin1_to_utf8(byte* conv_buf, Uint buf_sz, @@ -509,8 +508,6 @@ init_atom_table(void) atom_tab(ix)->name = (byte*)erl_atom_names[i]; } - /* Hide am_ErtsSecretAtom */ - hash_erase(&erts_atom_table.htable, atom_tab(atom_val(am_ErtsSecretAtom))); } void diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 6818a372f8..f0fa834d49 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -59,10 +59,6 @@ atom nocatch atom undefined_function atom undefined_lambda -# Secret internal atom that can never be found by string lookup -# and should never leak out to be seen by the user. -atom ErtsSecretAtom='3RT$' - # # Other commonly used atoms. # diff --git a/erts/emulator/beam/dist.h b/erts/emulator/beam/dist.h index 29982ed2a5..475d87a09f 100644 --- a/erts/emulator/beam/dist.h +++ b/erts/emulator/beam/dist.h @@ -63,6 +63,7 @@ #define DFLAG_NAME_ME (((Uint64)0x2) << 32) #define DFLAG_V4_NC (((Uint64)0x4) << 32) #define DFLAG_ALIAS (((Uint64)0x8) << 32) +#define DFLAG_LOCAL_EXT (((Uint64)0x10) << 32) /* internal */ /* * In term_to_binary/2, we will use DFLAG_ATOM_CACHE to mean @@ -282,10 +283,17 @@ typedef struct TTBEncodeContext_ { SysIOVec* iov; ErlDrvBinary** binv; Eterm *termv; - int iovec; Uint fragment_size; Sint frag_ix; ErlIOVec *fragment_eiovs; + int iovec; + int continue_make_lext_hash; + int lext_vlen; + byte *lext_hash; + union { + ErtsBlockHashState block; + ErtsIovBlockHashState iov_block; + } lext_state; #ifdef DEBUG int debug_fragments; int debug_vlen; @@ -305,6 +313,8 @@ typedef struct TTBEncodeContext_ { (Ctx)->iov = NULL; \ (Ctx)->binv = NULL; \ (Ctx)->fragment_size = ~((Uint) 0); \ + (Ctx)->continue_make_lext_hash = 0; \ + (Ctx)->lext_vlen = -1; \ if ((Flags) & DFLAG_PENDING_CONNECT) { \ (Ctx)->hopefull_flags = 0; \ (Ctx)->hopefull_flagsp = NULL; \ diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c index 7ec1e3919c..7df1e6a841 100644 --- a/erts/emulator/beam/erl_init.c +++ b/erts/emulator/beam/erl_init.c @@ -388,6 +388,7 @@ erl_init(int ncpu, erl_nif_init(); erts_msacc_init(); beamfile_init(); + erts_late_init_external(); } static Eterm diff --git a/erts/emulator/beam/erl_term_hashing.c b/erts/emulator/beam/erl_term_hashing.c index 0a894d4d2b..b31abceaf9 100644 --- a/erts/emulator/beam/erl_term_hashing.c +++ b/erts/emulator/beam/erl_term_hashing.c @@ -424,10 +424,6 @@ tail_recur: #define HCONST 0x9e3779b9UL /* the golden ratio; an arbitrary value */ -typedef struct { - Uint32 a,b,c; -} ErtsBlockHashHelperCtx; - #define BLOCK_HASH_BYTES_PER_ITER 12 /* The three functions below are separated into different functions even @@ -508,6 +504,192 @@ block_hash(byte *block, Uint block_length, Uint32 initval) &ctx); } +/* + * Note! erts_block_hash() and erts_iov_block_hash() *must* produce + * the same result if the I/O vector is flattened and contain the + * same bytes as the array. + */ + +void +erts_block_hash_init(ErtsBlockHashState *state, const byte *ptr, + Uint len, Uint32 initval) +{ + block_hash_setup(initval, &state->hctx); + state->ptr = ptr; + state->len = len; + state->tot_len = len; +} + +int +erts_block_hash(Uint32 *hashp, Uint *sizep, ErtsBlockHashState *state) +{ + byte *ptr = (byte *) state->ptr; + Uint len = state->len; + Sint flen; + Uint llen; + + do { + + if (*sizep < len) { + llen = *sizep; + llen -= llen % BLOCK_HASH_BYTES_PER_ITER; + if (len > llen + BLOCK_HASH_BYTES_PER_ITER) { + llen += BLOCK_HASH_BYTES_PER_ITER; + flen = -1; + break; + } + } + + /* do it all... */ + flen = len % BLOCK_HASH_BYTES_PER_ITER; + llen = len - flen; + + } while (0); + + block_hash_buffer(ptr, llen, &state->hctx); + + ptr += llen; + + if (flen < 0) { + state->ptr = ptr; + state->len -= llen; + *sizep = llen; + return 0; /* yield */ + } + + *hashp = block_hash_final_bytes(ptr, (Uint) flen, + state->tot_len, &state->hctx); + + *sizep = llen + flen; + + state->ptr = ptr + flen; + state->len = 0; + + return !0; /* done */ +} + +/* + * Note! erts_block_hash() and erts_iov_block_hash() *must* produce + * the same result if the I/O vector is flattened and contain the + * same bytes as the array. + */ + +void +erts_iov_block_hash_init(ErtsIovBlockHashState *state, SysIOVec *iov, + Uint vlen, Uint32 initval) +{ + block_hash_setup(initval, &state->hctx); + state->iov = iov; + state->vlen = vlen; + state->tot_len = 0; + state->vix = 0; + state->ix = 0; +} + +int +erts_iov_block_hash(Uint32 *hashp, Uint *sizep, ErtsIovBlockHashState *state) +{ + byte buf[BLOCK_HASH_BYTES_PER_ITER]; + ErtsBlockHashHelperCtx *hctx = &state->hctx; + SysIOVec *iov = state->iov; + Uint vlen = state->vlen; + int vix = state->vix; + int ix = state->ix; + Uint cix = 0; + byte *final_bytes; + Uint no_final_bytes; + Uint chunk_sz = (*sizep + - *sizep % BLOCK_HASH_BYTES_PER_ITER + + BLOCK_HASH_BYTES_PER_ITER); + + do { + Uint bsz, csz; + int left; + byte *ptr; + + ASSERT((cix % BLOCK_HASH_BYTES_PER_ITER) == 0); + + /* + * We may have empty vectors... + */ + while (ix == iov[vix].iov_len) { + vix++; + if (vix == vlen) { + final_bytes = NULL; + no_final_bytes = 0; + goto finalize; + } + ix = 0; + } + + csz = chunk_sz - cix; + left = iov[vix].iov_len - ix; + ptr = iov[vix].iov_base; + + if (left >= BLOCK_HASH_BYTES_PER_ITER) { + if (csz <= left) + bsz = csz; + else + bsz = left - (left % BLOCK_HASH_BYTES_PER_ITER); + block_hash_buffer(ptr + ix, bsz, hctx); + cix += bsz; + ix += bsz; + } + else { + int bix = 0; + bsz = left; + while (!0) { + sys_memcpy(&buf[bix], ptr + ix, bsz); + bix += bsz; + cix += bsz; + ix += bsz; + if (bix == BLOCK_HASH_BYTES_PER_ITER) { + block_hash_buffer(&buf[0], + (Uint) BLOCK_HASH_BYTES_PER_ITER, + hctx); + break; + } + ASSERT(ix == iov[vix].iov_len); + vix++; + if (vix == vlen) { + final_bytes = &buf[0]; + no_final_bytes = (Uint) bsz; + goto finalize; + } + ix = 0; + ptr = iov[vix].iov_base; + bsz = iov[vix].iov_len; + if (bsz > BLOCK_HASH_BYTES_PER_ITER - bix) + bsz = BLOCK_HASH_BYTES_PER_ITER - bix; + } + } + + } while (cix < chunk_sz); + + ASSERT((cix % BLOCK_HASH_BYTES_PER_ITER) == 0); + + /* yield */ + + *sizep = cix; + + state->tot_len += cix; + state->vix = vix; + state->ix = ix; + + return 0; + +finalize: + + state->tot_len += cix; + *sizep = cix; + + *hashp = block_hash_final_bytes(final_bytes, no_final_bytes, + state->tot_len, hctx); + return !0; /* done */ +} + + + typedef enum { tag_primary_list, arityval_subtag, diff --git a/erts/emulator/beam/erl_term_hashing.h b/erts/emulator/beam/erl_term_hashing.h index 79c28d52bd..db5c240a0a 100644 --- a/erts/emulator/beam/erl_term_hashing.h +++ b/erts/emulator/beam/erl_term_hashing.h @@ -22,16 +22,52 @@ #define ERL_TERM_HASHING_H__ #include "sys.h" +#include "erl_drv_nif.h" #if (defined(__aarch64__) && defined(__ARM_FEATURE_CRC32)) || \ (defined(__x86_64__) && defined(__SSE4_2__)) # define ERL_INTERNAL_HASH_CRC32C #endif +typedef struct { + Uint32 a,b,c; +} ErtsBlockHashHelperCtx; + +typedef struct { + ErtsBlockHashHelperCtx hctx; + const byte *ptr; + Uint len; + Uint tot_len; +} ErtsBlockHashState; + +typedef struct { + ErtsBlockHashHelperCtx hctx; + SysIOVec* iov; + Uint vlen; + Uint tot_len; + int vix; + int ix; +} ErtsIovBlockHashState; + Uint32 make_hash2(Eterm); Uint32 trapping_make_hash2(Eterm, Eterm*, struct process*); Uint32 make_hash(Eterm); Uint32 make_internal_hash(Eterm, Uint32 salt); Uint32 make_map_hash(Eterm key, int level); +void erts_block_hash_init(ErtsBlockHashState *state, + const byte *ptr, + Uint len, + Uint32 initval); +int erts_block_hash(Uint32 *hashp, + Uint *sizep, + ErtsBlockHashState *state); +void erts_iov_block_hash_init(ErtsIovBlockHashState *state, + SysIOVec *iov, + Uint vlen, + Uint32 initval); +int erts_iov_block_hash(Uint32 *hashp, + Uint *sizep, + ErtsIovBlockHashState *state); + #endif diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c index bfa08e07bc..a380454699 100644 --- a/erts/emulator/beam/external.c +++ b/erts/emulator/beam/external.c @@ -47,6 +47,8 @@ #include "erl_proc_sig_queue.h" #include "erl_trace.h" #include "erl_global_literals.h" +#include "erl_term_hashing.h" + #define PASS_THROUGH 'p' @@ -95,8 +97,8 @@ static byte* enc_atom(ErtsAtomCacheMap *, Eterm, byte*, Uint64); static byte* enc_pid(ErtsAtomCacheMap *, Eterm, byte*, Uint64); struct B2TContext_t; static const byte* dec_term(ErtsDistExternal*, ErtsHeapFactory*, const byte*, Eterm*, struct B2TContext_t*, int); -static const byte* dec_atom(ErtsDistExternal *, const byte*, Eterm*); -static const byte* dec_pid(ErtsDistExternal *, ErtsHeapFactory*, const byte*, Eterm*, byte tag); +static const byte* dec_atom(ErtsDistExternal *, const byte*, Eterm*, int); +static const byte* dec_pid(ErtsDistExternal *, ErtsHeapFactory*, const byte*, Eterm*, byte tag, int); static Sint decoded_size(const byte *ep, const byte* endp, int internal_tags, struct B2TContext_t*); static BIF_RETTYPE term_to_binary_trap_1(BIF_ALIST_1); @@ -104,7 +106,6 @@ static Eterm erts_term_to_binary_int(Process* p, Sint bif_ix, Eterm Term, Eterm Uint64 dflags, Binary *context_b, int iovec, Uint fragment_size); -static Uint encode_size_struct2(ErtsAtomCacheMap *, Eterm, Uint64); static ErtsExtSzRes encode_size_struct_int(TTBSizeContext*, ErtsAtomCacheMap *acmp, Eterm obj, Uint64 dflags, Sint *reds, Uint *res); @@ -126,6 +127,72 @@ void erts_init_external(void) { return; } +static Uint32 local_node_hash; + +void erts_late_init_external(void) +{ + char hname[256], pidstr[21]; + size_t hname_len, pidstr_len; + ErtsMonotonicTime mtime, toffs; + ErtsBlockHashState hstate; + byte *lnid; + Uint lnid_ix, chunk_size; + int res; + + res = sys_get_hostname(&hname[0], sizeof(hname)); + if (res == 0) { + hname_len = strlen(hname); + } + else { + hname[0] = '\0'; + hname_len = 0; + } + + sys_get_pid(&pidstr[0], sizeof(pidstr)); + pidstr[20] = '\0'; + + pidstr_len = strlen(pidstr); + + toffs = erts_get_time_offset(); + mtime = erts_get_monotonic_time(NULL); + + lnid = (byte *) erts_alloc(ERTS_ALC_T_TMP, 8 + hname_len + pidstr_len); + + lnid_ix = 0; + + /* time offset... */ + lnid[lnid_ix++] = (byte) toffs & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 8) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 16) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 24) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 32) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 40) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 48) & 0xff; + lnid[lnid_ix++] = (byte) (toffs >> 56) & 0xff; + + /* hostname... */ + + sys_memcpy(&lnid[lnid_ix], &hname[0], hname_len); + + lnid_ix += hname_len; + + /* pid... */ + memcpy(&lnid[lnid_ix], &pidstr[0], pidstr_len); + + /* + * Use least significant 32 bits of monotonic time as initial + * value to hash... + */ + erts_block_hash_init(&hstate, &lnid[0], lnid_ix, + (Uint32) (mtime & 0xffffffff)); + chunk_size = ERTS_UINT_MAX; + res = erts_block_hash(&local_node_hash, &chunk_size, &hstate); + ASSERT(res); (void) res; + ASSERT(chunk_size == lnid_ix); + + erts_free(ERTS_ALC_T_TMP, lnid); +} + #define ERTS_MAX_INTERNAL_ATOM_CACHE_ENTRIES 255 #define ERTS_DIST_HDR_ATOM_CACHE_FLAG_BYTE_IX(IIX) \ @@ -659,7 +726,7 @@ erts_encode_dist_ext_size(Eterm term, return res; } -ErtsExtSzRes erts_encode_ext_size_2(Eterm term, unsigned dflags, Uint *szp) +ErtsExtSzRes erts_encode_ext_size_2(Eterm term, Uint64 dflags, Uint *szp) { ErtsExtSzRes res; *szp = 0; @@ -675,8 +742,14 @@ ErtsExtSzRes erts_encode_ext_size(Eterm term, Uint *szp) Uint erts_encode_ext_size_ets(Eterm term) { - return encode_size_struct2(NULL, term, - TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED); + ErtsExtSzRes res; + Uint sz = 0; + + res = encode_size_struct_int(NULL, NULL, term, + TERM_TO_BINARY_DFLAGS|DFLAG_ETS_COMPRESSED, + NULL, &sz); + ASSERT(res == ERTS_EXT_SZ_OK); (void) res; + return sz; } @@ -1400,12 +1473,12 @@ BIF_RETTYPE term_to_iovec_1(BIF_ALIST_1) } static ERTS_INLINE int -parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep) +parse_t2b_opts(Eterm opts, Uint64 *flagsp, int *levelp, int *iovecp, Uint *fsizep) { int level = 0; int iovec = 0; - Uint flags = TERM_TO_BINARY_DFLAGS; - int deterministic = 0; + Uint64 flags = TERM_TO_BINARY_DFLAGS; + int deterministic = 0, local = 0; Uint fsize = ~((Uint) 0); /* one fragment */ while (is_list(opts)) { @@ -1418,6 +1491,8 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep) iovec = !0; } else if (arg == am_deterministic) { deterministic = 1; + } else if (arg == am_local) { + local = !0; } else if (is_tuple(arg) && *(tp = tuple_val(arg)) == make_arityval(2)) { if (tp[1] == am_minor_version && is_small(tp[2])) { switch (signed_val(tp[2])) { @@ -1460,6 +1535,14 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep) return 0; /* badarg */ } + if (deterministic && local) { + return 0; /* badarg */ + } + + if (local) { + flags |= DFLAG_LOCAL_EXT; + } + if (deterministic) { flags |= DFLAG_DETERMINISTIC; } @@ -1477,7 +1560,7 @@ parse_t2b_opts(Eterm opts, Uint *flagsp, int *levelp, int *iovecp, Uint *fsizep) BIF_RETTYPE term_to_binary_2(BIF_ALIST_2) { int level; - Uint flags; + Uint64 flags; Eterm res; if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) { @@ -1504,7 +1587,7 @@ BIF_RETTYPE term_to_binary_2(BIF_ALIST_2) BIF_RETTYPE term_to_iovec_2(BIF_ALIST_2) { int level; - Uint flags; + Uint64 flags; Eterm res; if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) { @@ -1533,7 +1616,7 @@ erts_debug_term_to_binary(Process *p, Eterm term, Eterm opts) { Eterm ret; int level, iovec; - Uint flags; + Uint64 flags; Uint fsize; if (!parse_t2b_opts(opts, &flags, &level, &iovec, &fsize)) { @@ -1580,9 +1663,12 @@ enum B2TState { /* order is somewhat significant */ typedef struct { Sint heap_size; - int terms; - const byte* ep; + Uint32 terms; int atom_extra_skip; + const byte* ep; + ErtsBlockHashState lext_state; + const byte *lext_hash; + Uint32 lext_term_end; } B2TSizeContext; typedef struct { @@ -1591,6 +1677,7 @@ typedef struct { Eterm* next; ErtsHeapFactory factory; int remaining_n; + int internal_nc; char* remaining_bytes; ErtsPStack map_array; } B2TDecodeContext; @@ -1820,7 +1907,6 @@ static BIF_RETTYPE binary_to_term_trap_1(BIF_ALIST_1) return binary_to_term_int(BIF_P, THE_NON_VALUE, ERTS_MAGIC_BIN_DATA(context_bin)); } - #define B2T_BYTES_PER_REDUCTION 128 #define B2T_MEMCPY_FACTOR 8 @@ -1947,6 +2033,7 @@ static BIF_RETTYPE binary_to_term_int(Process* p, Eterm bin, B2TContext *ctx) ctx->u.dc.ep = ctx->b2ts.extp; ctx->u.dc.res = (Eterm) (UWord) NULL; ctx->u.dc.next = &ctx->u.dc.res; + ctx->u.dc.internal_nc = 0; erts_factory_proc_prealloc_init(&ctx->u.dc.factory, p, ctx->heap_size); ctx->u.dc.map_array.pstart = NULL; ctx->state = B2TDecode; @@ -2118,34 +2205,11 @@ Eterm external_size_2(BIF_ALIST_2) { Uint size = 0; - Uint flags = TERM_TO_BINARY_DFLAGS; - - while (is_list(BIF_ARG_2)) { - Eterm arg = CAR(list_val(BIF_ARG_2)); - Eterm* tp; + int level; + Uint64 flags; - if (is_tuple(arg) && *(tp = tuple_val(arg)) == make_arityval(2)) { - if (tp[1] == am_minor_version && is_small(tp[2])) { - switch (signed_val(tp[2])) { - case 0: - flags &= ~DFLAG_NEW_FLOATS; - break; - case 1: - break; - default: - goto error; - } - } else { - goto error; - } - } else { - error: - BIF_ERROR(BIF_P, BADARG); - } - BIF_ARG_2 = CDR(list_val(BIF_ARG_2)); - } - if (is_not_nil(BIF_ARG_2)) { - goto error; + if (!parse_t2b_opts(BIF_ARG_2, &flags, &level, NULL, NULL)) { + BIF_ERROR(BIF_P, BADARG); } switch (erts_encode_ext_size_2(BIF_ARG_1, flags, &size)) { @@ -2817,45 +2881,80 @@ enc_atom(ErtsAtomCacheMap *acmp, Eterm atom, byte *ep, Uint64 dflags) } /* - * We use this atom as sysname in local pid/port/refs - * for the ETS compressed format + * We use INTERNAL_LOCAL_SYSNAME to mark internal node for pid/port/refs + * in the ETS compressed format and local format. * */ -#define INTERNAL_LOCAL_SYSNAME am_ErtsSecretAtom +#define INTERNAL_LOCAL_SYSNAME NIL -static byte* -enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags) +static ERTS_INLINE byte * +enc_internal_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags) { - Uint on, os; - Eterm sysname = ((is_internal_pid(pid) && (dflags & DFLAG_ETS_COMPRESSED)) - ? INTERNAL_LOCAL_SYSNAME : pid_node_name(pid)); - Uint32 creation = pid_creation(pid); + Uint32 number, serial, creation; - *ep++ = NEW_PID_EXT; + ASSERT(is_internal_pid(pid)); - ep = enc_atom(acmp, sysname, ep, dflags); + *ep++ = NEW_PID_EXT; - if (is_internal_pid(pid)) { - on = internal_pid_number(pid); - os = internal_pid_serial(pid); + number = internal_pid_number(pid); + serial = internal_pid_serial(pid); + if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) { + *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */ + creation = 0; } else { - on = external_pid_number(pid); - os = external_pid_serial(pid); + Eterm sysname = internal_pid_node_name(pid); + creation = internal_pid_creation(pid); + ep = enc_atom(acmp, sysname, ep, dflags); } - put_int32(on, ep); + put_int32(number, ep); + ep += 4; + put_int32(serial, ep); + ep += 4; + put_int32(creation, ep); + ep += 4; + return ep; + +} + +static ERTS_INLINE byte * +enc_external_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags) +{ + Uint32 number, serial, creation; + Eterm sysname; + + ASSERT(is_external_pid(pid)); + + *ep++ = NEW_PID_EXT; + + ASSERT(is_external_pid(pid)); + number = external_pid_number(pid); + serial = external_pid_serial(pid); + sysname = external_pid_node_name(pid); + creation = external_pid_creation(pid); + ep = enc_atom(acmp, sysname, ep, dflags); + + put_int32(number, ep); ep += 4; - put_int32(os, ep); + put_int32(serial, ep); ep += 4; put_int32(creation, ep); ep += 4; return ep; } +static byte* +enc_pid(ErtsAtomCacheMap *acmp, Eterm pid, byte* ep, Uint64 dflags) +{ + if (is_internal_pid(pid)) + return enc_internal_pid(acmp, pid, ep, dflags); + return enc_external_pid(acmp, pid, ep, dflags); +} + /* Expect an atom in plain text or cached */ static const byte* -dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp) +dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp, int internal_nc) { Uint len; int n; @@ -2920,7 +3019,12 @@ dec_atom(ErtsDistExternal *edep, const byte* ep, Eterm* objp) } *objp = make_atom(n); break; - + case NIL_EXT: + if (internal_nc) { + *objp = INTERNAL_LOCAL_SYSNAME; + break; + } + /* else: fail... */ default: error: *objp = NIL; /* Don't leave a hole in the heap */ @@ -2948,7 +3052,7 @@ static ERTS_INLINE ErlNode* dec_get_node(Eterm sysname, Uint32 creation, Eterm b static const byte* dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, const byte* ep, - Eterm* objp, byte tag) + Eterm* objp, byte tag, int internal_nc) { Eterm sysname; Uint data; @@ -2959,7 +3063,7 @@ dec_pid(ErtsDistExternal *edep, ErtsHeapFactory* factory, const byte* ep, *objp = NIL; /* In case we fail, don't leave a hole in the heap */ /* eat first atom */ - if ((ep = dec_atom(edep, ep, &sysname)) == NULL) + if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) return NULL; num = get_uint32(ep); ep += 4; @@ -3073,6 +3177,7 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, FloatDef f; register Sint r = 0; int use_iov = 0; + byte *lext_hash = NULL; /* initialize to avoid faulty warning... */ /* The following variables are only used during encoding of * a map when the `deterministic` option is active. */ @@ -3090,10 +3195,39 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, obj = ctx->obj; map_array = ctx->map_array; next_map_element = ctx->next_map_element; + lext_hash = ctx->lext_hash; if (is_non_value(obj)) { goto outer_loop; } + else { + goto L_jump_start; + } } + if (ctx->continue_make_lext_hash) { + lext_hash = ctx->lext_hash; + ep = ctx->ep; + if (use_iov) { + goto continue_make_lext_hash_iov; + } + else { + goto continue_make_lext_hash_bin; + } + } + } + + /* We only pass here once at the start of the encoding... */ + if (dflags & DFLAG_LOCAL_EXT) { + *ep++ = LOCAL_EXT; + lext_hash = ep; + if (ctx) + ctx->lext_hash = ep; + ep += 4; /* 32-bit hash */ + if (use_iov) { + ASSERT(ctx); + store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0); + /* current vlen is now where to start calculating the hash */ + ctx->lext_vlen = ctx->vlen; + } } goto L_jump_start; @@ -3362,54 +3496,89 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, } break; - case PID_DEF: case EXTERNAL_PID_DEF: - ep = enc_pid(acmp, obj, ep, dflags); + ep = enc_external_pid(acmp, obj, ep, dflags); + break; + + case PID_DEF: + ep = enc_internal_pid(acmp, obj, ep, dflags); break; - case REF_DEF: case EXTERNAL_REF_DEF: { + Eterm sysname; Uint32 *ref_num; - Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_ref(obj)) - ? INTERNAL_LOCAL_SYSNAME : ref_node_name(obj)); - Uint32 creation = ref_creation(obj); + Uint32 creation; + + *ep++ = NEWER_REFERENCE_EXT; + + ref_num = external_ref_numbers(obj); + i = external_ref_no_numbers(obj); + put_int16(i, ep); + ep += 2; + + sysname = external_ref_node_name(obj); + creation = external_ref_creation(obj); + goto ref_common; - erts_magic_ref_save_bin(obj); + case REF_DEF: *ep++ = NEWER_REFERENCE_EXT; - i = ref_no_numbers(obj); - put_int16(i, ep); - ep += 2; - ep = enc_atom(acmp, sysname, ep, dflags); + + erts_magic_ref_save_bin(obj); + + ref_num = internal_ref_numbers(obj); + i = internal_ref_no_numbers(obj); + put_int16(i, ep); + ep += 2; + + if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) { + *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */ + creation = 0; + } + else { + sysname = internal_ref_node_name(obj); + creation = internal_ref_creation(obj); + ref_common: + ep = enc_atom(acmp, sysname, ep, dflags); + } + put_int32(creation, ep); ep += 4; - ref_num = ref_numbers(obj); for (j = 0; j < i; j++) { put_int32(ref_num[j], ep); ep += 4; } break; } - case PORT_DEF: case EXTERNAL_PORT_DEF: { - Eterm sysname = (((dflags & DFLAG_ETS_COMPRESSED) && is_internal_port(obj)) - ? INTERNAL_LOCAL_SYSNAME : port_node_name(obj)); - Uint32 creation = port_creation(obj); - byte *tagp = ep++; - Uint64 num; - - ep = enc_atom(acmp, sysname, ep, dflags); - num = port_number(obj); - if (num > ERTS_MAX_V3_PORT_NUMBER) { - *tagp = V4_PORT_EXT; - put_int64(num, ep); - ep += 8; - } - else { - *tagp = NEW_PORT_EXT; - put_int32(num, ep); - ep += 4; - } + Eterm sysname; + Uint64 number; + Uint32 creation; + + *ep++ = V4_PORT_EXT; + number = external_port_number(obj); + sysname = external_port_node_name(obj); + creation = external_port_creation(obj); + + goto port_common; + + case PORT_DEF: + + *ep++ = V4_PORT_EXT; + number = internal_port_number(obj); + if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) { + *ep++ = NIL_EXT; /* INTERNAL_LOCAL_NODE */ + creation = 0; + } + else { + sysname = internal_port_node_name(obj); + creation = internal_port_creation(obj); + port_common: + ep = enc_atom(acmp, sysname, ep, dflags); + } + + put_int64(number, ep); + ep += 8; put_int32(creation, ep); ep += 4; break; @@ -3743,6 +3912,68 @@ enc_term_int(TTBEncodeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, byte* ep, if (use_iov) store_in_vec(ctx, ep, NULL, THE_NON_VALUE, NULL, 0); } + if (dflags & DFLAG_LOCAL_EXT) { + int done; + Uint32 hash; + Uint chunk_len; + + if (use_iov) { + ASSERT(ctx); + erts_iov_block_hash_init(&ctx->lext_state.iov_block, + &ctx->iov[ctx->lext_vlen], + ctx->vlen - ctx->lext_vlen, + local_node_hash); + + continue_make_lext_hash_iov: + /* Do 128 bytes per reduction... */ + chunk_len = (Uint) r*128; + + done = erts_iov_block_hash(&hash, &chunk_len, + &ctx->lext_state.iov_block); + } + else { + ErtsBlockHashState lext_state_buf, *lext_state; + byte *ep_start = lext_hash + 4 /* 32 bit hash value */; + Sint len = ep - ep_start; + + ASSERT(len >= 0); + + lext_state = ctx ? &ctx->lext_state.block : &lext_state_buf; + + erts_block_hash_init(lext_state, ep_start, len, local_node_hash); + + if (!ctx) { + /* Do it all at once... */ + chunk_len = ERTS_UINT_MAX; + } + else { + continue_make_lext_hash_bin: + lext_state = &ctx->lext_state.block; + /* Do 128 bytes per reduction... */ + chunk_len = (Uint) r*128; + } + + done = erts_block_hash(&hash, &chunk_len, lext_state); + } + + if (!ctx) { + ASSERT(done); + } + else { + if (!done) { + /* yield; more work calculating hash... */ + ctx->ep = ep; + ctx->continue_make_lext_hash = !0; + *reds = 0; + return -1; + } + r -= chunk_len/128; + *reds = r; + } + + put_int32(hash, lext_hash); + } + *res = ep; return 0; } @@ -3926,7 +4157,7 @@ dec_term(ErtsDistExternal *edep, { #define PSTACK_TYPE struct dec_term_map PSTACK_DECLARE(map_array, 10); - int n; + int n, internal_nc = ets_decode; ErtsAtomEncoding char_enc; register Eterm* hp; /* Please don't take the address of hp */ Eterm* next; @@ -3940,6 +4171,7 @@ dec_term(ErtsDistExternal *edep, next = ctx->u.dc.next; ep = ctx->u.dc.ep; factory = &ctx->u.dc.factory; + internal_nc = ctx->u.dc.internal_nc; if (ctx->state != B2TDecode) { int n_limit = reds; @@ -4031,6 +4263,8 @@ dec_term(ErtsDistExternal *edep, objp = next; next = (Eterm *) *objp; + continue_this_obj: + switch (*ep++) { case INTEGER_EXT: { @@ -4274,7 +4508,7 @@ dec_term_atom_common: case PID_EXT: case NEW_PID_EXT: factory->hp = hp; - ep = dec_pid(edep, factory, ep, objp, ep[-1]); + ep = dec_pid(edep, factory, ep, objp, ep[-1], internal_nc); hp = factory->hp; if (ep == NULL) { goto error; @@ -4290,7 +4524,7 @@ dec_term_atom_common: Uint32 cre; byte tag = ep[-1]; - if ((ep = dec_atom(edep, ep, &sysname)) == NULL) { + if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) { goto error; } if (tag == V4_PORT_EXT) { @@ -4350,7 +4584,7 @@ dec_term_atom_common: ref_words = 1; - if ((ep = dec_atom(edep, ep, &sysname)) == NULL) + if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) goto error; if ((r0 = get_int32(ep)) >= MAX_REFERENCE ) goto error; @@ -4367,7 +4601,7 @@ dec_term_atom_common: ref_words = get_int16(ep); ep += 2; - if ((ep = dec_atom(edep, ep, &sysname)) == NULL) + if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) goto error; cre = get_int8(ep); @@ -4385,7 +4619,7 @@ dec_term_atom_common: ref_words = get_int16(ep); ep += 2; - if ((ep = dec_atom(edep, ep, &sysname)) == NULL) + if ((ep = dec_atom(edep, ep, &sysname, internal_nc)) == NULL) goto error; cre = get_int32(ep); @@ -4623,10 +4857,10 @@ dec_term_atom_common: Eterm temp; Sint arity; - if ((ep = dec_atom(edep, ep, &mod)) == NULL) { + if ((ep = dec_atom(edep, ep, &mod, 0)) == NULL) { goto error; } - if ((ep = dec_atom(edep, ep, &name)) == NULL) { + if ((ep = dec_atom(edep, ep, &name, 0)) == NULL) { goto error; } factory->hp = hp; @@ -4741,7 +4975,7 @@ dec_term_atom_common: *objp = make_fun(funp); /* Module */ - if ((ep = dec_atom(edep, ep, &module)) == NULL) { + if ((ep = dec_atom(edep, ep, &module, 0)) == NULL) { goto error; } factory->hp = hp; @@ -4854,6 +5088,13 @@ dec_term_atom_common: break; } + case LOCAL_EXT: + internal_nc = !0; + if (ctx) + ctx->u.dc.internal_nc = !0; + ep += 4; /* 32-bit hash (verified in decoded_size()) */ + goto continue_this_obj; + default: goto error; } @@ -4954,23 +5195,88 @@ error_map_fixup: return NULL; } -/* returns the number of bytes needed to encode an object - to a sequence of bytes - N.B. That this must agree with to_external2() above!!! - (except for cached atoms) */ -static Uint encode_size_struct2(ErtsAtomCacheMap *acmp, - Eterm obj, - Uint64 dflags) { - Uint size = 0; - ErtsExtSzRes res = encode_size_struct_int(NULL, acmp, obj, - dflags, NULL, - &size); - /* - * encode_size_struct2() only allowed when - * we know the result will always be OK! - */ - ASSERT(res == ERTS_EXT_SZ_OK); (void) res; - return (Uint) size; +static Uint +encode_atom_size(ErtsAtomCacheMap *acmp, Eterm atom, Uint64 dflags) +{ + ASSERT(is_atom(atom)); + if (dflags & DFLAG_ETS_COMPRESSED) { + if (atom_val(atom) >= (1<<16)) { + return (Uint) 1 + 3; + } + else { + return (Uint) 1 + 2; + } + } + else { + Atom *a = atom_tab(atom_val(atom)); + int alen; + Uint result; + if ((dflags & DFLAG_UTF8_ATOMS) || a->latin1_chars < 0) { + alen = a->len; + result = (Uint) 1 + 1 + alen; + if (alen > 255) { + result++; /* ATOM_UTF8_EXT (not small) */ + } + } + else { + alen = a->latin1_chars; + result = (Uint) 1 + 1 + alen; + if (alen > 255 || !(dflags & DFLAG_SMALL_ATOM_TAGS)) + result++; /* ATOM_EXT (not small) */ + } + insert_acache_map(acmp, atom, dflags); + return result; + } +} + +static Uint +encode_internal_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags) +{ + int nlen; + ASSERT(is_internal_pid(pid)); + nlen = ((dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) + ? 1 + : encode_atom_size(acmp, internal_pid_node_name(pid), dflags)); + return (Uint) 1 + nlen + 4 + 4 + 4; +} + +static Uint +encode_external_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags) +{ + int nlen; + ASSERT(is_external_pid(pid)); + nlen = encode_atom_size(acmp, external_pid_node_name(pid), dflags); + return (Uint) 1 + nlen + 4 + 4 + 4; +} + +static Uint +encode_pid_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags) +{ + if (is_internal_pid(pid)) + return encode_internal_pid_size(acmp, pid, dflags); + ASSERT(is_external_pid(pid)); + return encode_external_pid_size(acmp, pid, dflags); +} + +static Uint +encode_small_size(ErtsAtomCacheMap *acmp, Eterm pid, Uint64 dflags) +{ + Sint val = signed_val(pid); + Uint result; + + if ((Uint)val < 256) + result = (Uint) 1 + 1; /* SMALL_INTEGER_EXT */ + else if (sizeof(Sint) == 4 || IS_SSMALL32(val)) + result = (Uint) 1 + 4; /* INTEGER_EXT */ + else { + int i; + DeclareTmpHeapNoproc(tmp_big,2); + UseTmpHeapNoproc(2); + i = big_bytes(small_to_big(val, tmp_big)); + result = (Uint) 1 + 1 + 1 + i; /* SMALL_BIG_EXT */ + UnUseTmpHeapNoproc(2); + } + return result; } static ErtsExtSzRes @@ -4983,14 +5289,24 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, Sint r = 0; int vlen = -1; - if (ctx) { + if (!ctx) { + if (dflags & DFLAG_LOCAL_EXT) + result += 5; + } + else { WSTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK); r = *reds; vlen = ctx->vlen; - if (!ctx->wstack.wstart) + if (!ctx->wstack.wstart) { ctx->last_result = result; + if (dflags & DFLAG_LOCAL_EXT) { + result += 5; + if (vlen >= 0) + vlen++; + } + } else { /* restore saved stack */ WSTACK_RESTORE(s, &ctx->wstack); result = ctx->result; @@ -5019,49 +5335,10 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, result++; break; case ATOM_DEF: - if (dflags & DFLAG_ETS_COMPRESSED) { - if (atom_val(obj) >= (1<<16)) { - result += 1 + 3; - } - else { - result += 1 + 2; - } - } - else { - Atom *a = atom_tab(atom_val(obj)); - int alen; - if ((dflags & DFLAG_UTF8_ATOMS) || a->latin1_chars < 0) { - alen = a->len; - result += 1 + 1 + alen; - if (alen > 255) { - result++; /* ATOM_UTF8_EXT (not small) */ - } - } - else { - alen = a->latin1_chars; - result += 1 + 1 + alen; - if (alen > 255 || !(dflags & DFLAG_SMALL_ATOM_TAGS)) - result++; /* ATOM_EXT (not small) */ - } - insert_acache_map(acmp, obj, dflags); - } + result += encode_atom_size(acmp, obj, dflags); break; case SMALL_DEF: - { - Sint val = signed_val(obj); - - if ((Uint)val < 256) - result += 1 + 1; /* SMALL_INTEGER_EXT */ - else if (sizeof(Sint) == 4 || IS_SSMALL32(val)) - result += 1 + 4; /* INTEGER_EXT */ - else { - DeclareTmpHeapNoproc(tmp_big,2); - UseTmpHeapNoproc(2); - i = big_bytes(small_to_big(val, tmp_big)); - result += 1 + 1 + 1 + i; /* SMALL_BIG_EXT */ - UnUseTmpHeapNoproc(2); - } - } + result += encode_small_size(acmp, obj, dflags); break; case BIG_DEF: i = big_bytes(obj); @@ -5073,22 +5350,47 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, result += 1 + 4 + 1 + i; /* tag,size,sign,digits */ break; case EXTERNAL_PID_DEF: + result += encode_external_pid_size(acmp, obj, dflags); + break; case PID_DEF: - result += (1 + encode_size_struct2(acmp, pid_node_name(obj), dflags) + - 4 + 4 + 4); + result += encode_internal_pid_size(acmp, obj, dflags); break; - case EXTERNAL_REF_DEF: - case REF_DEF: - i = ref_no_numbers(obj); - result += (1 + 2 + encode_size_struct2(acmp, ref_node_name(obj), dflags) + - 4 + 4*i); + case EXTERNAL_REF_DEF: { + int nlen = encode_atom_size(acmp, + external_ref_node_name(obj), + dflags); + i = external_ref_no_numbers(obj); + result += (1 + 2 + nlen + 4 + 4*i); break; - case EXTERNAL_PORT_DEF: + } + case REF_DEF: { + int nlen; + i = internal_ref_no_numbers(obj); + if (dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) { + nlen = 1; + } + else { + nlen = encode_atom_size(acmp, + internal_ref_node_name(obj), + dflags); + } + result += (1 + 2 + nlen + 4 + 4*i); + break; + } + case EXTERNAL_PORT_DEF: { + int nlen = encode_atom_size(acmp, + external_port_node_name(obj), + dflags); + result += (1 + nlen + 8 + 4); + break; + } case PORT_DEF: { - Uint64 num = port_number(obj); - result += (num > ERTS_MAX_V3_PORT_NUMBER) ? 8 : 4; - result += (1 + encode_size_struct2(acmp, port_node_name(obj), dflags) - /* num */ + 4); + int nlen = ((dflags & (DFLAG_ETS_COMPRESSED|DFLAG_LOCAL_EXT)) + ? 1 + : encode_atom_size(acmp, + internal_port_node_name(obj), + dflags)); + result += (1 + nlen + 8 + 4); break; } case LIST_DEF: { @@ -5275,8 +5577,8 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, if (is_local_fun(funp)) { result += 20+1+1+4; /* New ID + Tag */ result += 4; /* Length field (number of free variables */ - result += encode_size_struct2(acmp, funp->creator, dflags); - result += encode_size_struct2(acmp, funp->entry.fun->module, dflags); + result += encode_pid_size(acmp, funp->creator, dflags); + result += encode_atom_size(acmp, funp->entry.fun->module, dflags); result += 2 * (1+4); /* Index, Uniq */ if (funp->num_free > 1) { WSTACK_PUSH2(s, (UWord) (funp->env + 1), @@ -5292,9 +5594,9 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, ASSERT(is_external_fun(funp) && funp->next == NULL); result += 1; - result += encode_size_struct2(acmp, ep->info.mfa.module, dflags); - result += encode_size_struct2(acmp, ep->info.mfa.function, dflags); - result += encode_size_struct2(acmp, make_small(ep->info.mfa.arity), dflags); + result += encode_atom_size(acmp, ep->info.mfa.module, dflags); + result += encode_atom_size(acmp, ep->info.mfa.function, dflags); + result += encode_small_size(acmp, make_small(ep->info.mfa.arity), dflags); } break; } @@ -5350,8 +5652,6 @@ encode_size_struct_int(TTBSizeContext* ctx, ErtsAtomCacheMap *acmp, Eterm obj, return ERTS_EXT_SZ_OK; } - - static Sint decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ctx) { @@ -5359,6 +5659,8 @@ decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ct int atom_extra_skip; Uint n; SWord reds; + const byte *lext_hash; + Uint32 lext_term_end; /* Keep track of the current number of sub terms remaining to be decoded. * @@ -5371,24 +5673,6 @@ decoded_size(const byte *ep, const byte* endp, int internal_tags, B2TContext* ct */ Uint32 terms; - if (ctx) { - reds = ctx->reds; - if (ctx->u.sc.ep) { - heap_size = ctx->u.sc.heap_size; - terms = ctx->u.sc.terms; - ep = ctx->u.sc.ep; - atom_extra_skip = ctx->u.sc.atom_extra_skip; - goto init_done; - } - } - else - ERTS_UNDEF(reds, 0); - - heap_size = 0; - terms = 1; - atom_extra_skip = 0; -init_done: - #define SKIP(sz) \ do { \ if ((sz) <= endp-ep) { \ @@ -5418,6 +5702,38 @@ init_done: if (terms < before) goto error; \ } while (0) + + if (ctx) { + reds = ctx->reds; + if (ctx->u.sc.ep) { + ep = ctx->u.sc.ep; + atom_extra_skip = ctx->u.sc.atom_extra_skip; + heap_size = ctx->u.sc.heap_size; + lext_hash = ctx->u.sc.lext_hash; + lext_term_end = ctx->u.sc.lext_term_end; + terms = ctx->u.sc.terms; + if (terms == lext_term_end) { + ASSERT(lext_hash); + goto continue_check_lext; + } + goto init_done; + } + } + else + ERTS_UNDEF(reds, 0); + + heap_size = 0; + terms = 1; + atom_extra_skip = 0; + /* + * lext_hash != NULL and local_term_end != ~0 when decoding local external + * term format... + */ + lext_hash = NULL; + lext_term_end = ~0; + +init_done: + ASSERT(terms > 0); do { int tag; @@ -5546,6 +5862,18 @@ init_done: terms++; break; case NIL_EXT: + if (atom_extra_skip) { + /* + * atom_extra_skip != 0 should only appear due to local encoding, + * or compressed ets encoding, of a node name in internal + * pids/ports/refs. If not currently inside a local encoding, + * this is an error... + */ + if (!lext_hash && !internal_tags) + goto error; + SKIP(atom_extra_skip); + atom_extra_skip = 0; + } break; case LIST_EXT: CHKSIZE(4); @@ -5692,22 +6020,87 @@ init_done: SKIP(2+sizeof(ProcBin)); heap_size += PROC_BIN_SIZE + ERL_SUB_BIN_SIZE; break; + CHKSIZE(1); + case LOCAL_EXT: + /* + * Currently the hash is 4 bytes large... + */ + CHKSIZE(4); + lext_hash = ep; + lext_term_end = terms - 1; + terms++; + ep += 4; + break; default: goto error; } terms--; + if (terms == lext_term_end) { + ErtsBlockHashState lext_state_buf, *lext_state; + const byte *ep_start = lext_hash + 4 /* 32 bit hash value */; + Sint len = ep - ep_start; + Uint chunk_len; + Uint32 hash; + + ASSERT(lext_hash); + + if (len <= 0) { + /* no terms */ + goto error; + } + + lext_state = ctx ? &ctx->u.sc.lext_state : &lext_state_buf; + + erts_block_hash_init(lext_state, ep_start, len, local_node_hash); + + if (!ctx) { + /* Do it all at once... */ + chunk_len = ERTS_UINT_MAX; + } + else { + continue_check_lext: + lext_state = &ctx->u.sc.lext_state; + /* 'reds' has been scaled up to "bytes per reds"... */ + chunk_len = (Uint) reds; + } + + if (!erts_block_hash(&hash, &chunk_len, lext_state)) { + /* yield; more work calculating hash... */ + ASSERT(ctx); + reds = 0; + } + else { + reds -= chunk_len; + if (hash != get_int32(lext_hash)) { + /* + * Hash presented in external format did not match the + * calculated hash... + */ + goto error; + } + lext_hash = NULL; + lext_term_end = ~0; + } + + } + + ASSERT(terms != ~0); + if (ctx && --reds <= 0 && terms != 0) { ctx->u.sc.heap_size = heap_size; ctx->u.sc.terms = terms; ctx->u.sc.ep = ep; ctx->u.sc.atom_extra_skip = atom_extra_skip; + ctx->u.sc.lext_hash = lext_hash; + ctx->u.sc.lext_term_end = lext_term_end; ctx->reds = 0; return 0; } } while (terms != 0); if (terms == 0) { + if (ctx) { ctx->state = B2TDecodeInit; ctx->reds = reds; diff --git a/erts/emulator/beam/external.h b/erts/emulator/beam/external.h index 7a59d02102..2d2a02b23e 100644 --- a/erts/emulator/beam/external.h +++ b/erts/emulator/beam/external.h @@ -57,6 +57,7 @@ #define ATOM_UTF8_EXT 'v' #define SMALL_ATOM_UTF8_EXT 'w' #define V4_PORT_EXT 'x' +#define LOCAL_EXT 'y' #define DIST_HEADER 'D' #define DIST_FRAG_HEADER 'E' @@ -187,7 +188,7 @@ int erts_encode_dist_ext(Eterm, byte **, Uint64, ErtsAtomCacheMap *, struct TTBEncodeContext_ *, Uint *, Sint *); ErtsExtSzRes erts_encode_ext_size(Eterm, Uint *szp); -ErtsExtSzRes erts_encode_ext_size_2(Eterm, unsigned, Uint *szp); +ErtsExtSzRes erts_encode_ext_size_2(Eterm, Uint64, Uint *szp); Uint erts_encode_ext_size_ets(Eterm); void erts_encode_ext(Eterm, byte **); byte* erts_encode_ext_ets(Eterm, byte *, struct erl_off_heap_header** ext_off_heap); diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 52d054a212..11f34b5444 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -1466,6 +1466,7 @@ Eterm erts_debug_persistent_term_xtra_info(Process* c_p); /* external.c */ void erts_init_external(void); +void erts_late_init_external(void); /* erl_map.c */ void erts_init_map(void); diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index b57cfd6952..821bc9f2c2 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -847,6 +847,7 @@ int sys_double_to_chars(double, char*, size_t); int sys_double_to_chars_ext(double, char*, size_t, size_t); int sys_double_to_chars_fast(double, char*, int, int, int); void sys_get_pid(char *, size_t); +int sys_get_hostname(char *buf, size_t size); /* erl_drv_get/putenv have been implicitly 8-bit for so long that we can't * change them without breaking things on Windows. Their return values are diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index 7ff8425d52..26959a9716 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -814,6 +814,10 @@ void sys_get_pid(char *buffer, size_t buffer_size){ erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p); } +int sys_get_hostname(char *buf, size_t size) +{ + return gethostname(buf, size); +} void sys_init_io(void) { } void erts_sys_alloc_init(void) { } diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index 79f003c7c9..1fc8801e56 100644 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -35,6 +35,10 @@ #include "erl_cpu_topology.h" #include <malloc.h> +#if defined(__WIN32__) && !defined(WINDOWS_H_INCLUDES_WINSOCK2_H) +#include <winsock2.h> +#endif + void erts_sys_init_float(void); void erl_start(int, char**); @@ -2767,6 +2771,11 @@ void sys_get_pid(char *buffer, size_t buffer_size){ erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p); } +int sys_get_hostname(char *buf, size_t size) +{ + return gethostname(buf, size); +} + void sys_init_io(void) { diff --git a/erts/emulator/test/binary_SUITE.erl b/erts/emulator/test/binary_SUITE.erl index b8683385cf..cf63cce3d7 100644 --- a/erts/emulator/test/binary_SUITE.erl +++ b/erts/emulator/test/binary_SUITE.erl @@ -77,7 +77,8 @@ error_after_yield/1, cmp_old_impl/1, t2b_system_limit/1, term_to_iovec/1, - is_binary_test/1]). + is_binary_test/1, + local_ext/1]). %% Internal exports. -export([sleeper/0,trapping_loop/4]). @@ -106,7 +107,8 @@ all() -> bit_sized_binary_sizes, otp_6817, otp_8117, deep, robustness, otp_8180, trapping, large, error_after_yield, cmp_old_impl, - is_binary_test]. + is_binary_test, + local_ext]. groups() -> [ @@ -2348,3 +2350,177 @@ list2bitstrlist([X0, X1, X2, X3, X4, X5 | Xs], Acc) when is_integer(X0), 0 =< X0 list2bitstrlist(Xs, NewAcc); list2bitstrlist([X | Xs], Acc) -> list2bitstrlist(Xs, [Acc,X]). + +local_ext(Config) when is_list(Config) -> + SDrv = send_term_local_drv, + CDrv = call_local_drv, + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + FileName = filename:join(PrivDir, "local_ext.data"), + Args = ["-setcookie", atom_to_list(erlang:get_cookie()), + "-pa", filename:dirname(code:which(?MODULE))], + {ok, Peer1, _} = peer:start_link(#{connection => 0, args => Args}), + {ok, Peer2, _} = peer:start_link(#{connection => 0, args => Args}), + LongNames = net_kernel:longnames(), + DynStartOpts = #{name_domain => if LongNames -> longnames; + true -> shortnames + end}, + ExternalPid = self(), + ExternalRef = make_ref(), + ExternalPort = hd(erlang:ports()), + EncDecLocal = fun () -> + erl_ddll:start(), + ok = erl_ddll:load_driver(DataDir, SDrv), + SPort = open_port({spawn, SDrv}, []), + ok = erl_ddll:load_driver(DataDir, CDrv), + CPort = open_port({spawn, CDrv}, []), + false = erlang:is_alive(), + nonode@nohost = node(), + LocalPid = self(), + LocalRef = make_ref(), + LocalPort = hd(erlang:ports()), + Bin1 = <<4711:800>>, + Bin2 = <<4711:703>>, + Bin3 = <<4711:600>>, + Terms = [ + LocalPid, + ExternalPid, + LocalRef, + ExternalRef, + LocalPort, + ExternalPort, + [LocalPid, Bin1, ExternalPid, LocalRef, Bin2, + ExternalRef, Bin3, Bin2, LocalPort, + ExternalPort], + "hej", + [], + {processes(), Bin3, erlang:ports(), Bin3}, + #{pid => LocalPid, ref => LocalRef, port => LocalPort} + ], + {ok, FD} = file:open(FileName, [write]), + ETs = lists:map(fun (Term) -> + {enc_local(FD, Term), Term} + end, Terms), + ok = file:close(FD), + CheckET = fun ({LExt, Term}) -> + Term = binary_to_term(LExt), + SPort ! {self(), {command, LExt}}, + receive + {SPort, Reply} -> + Term = Reply + end + end, + lists:foreach(CheckET, ETs), + call_local_success(CPort, ETs), + NodeName = peer:random_name(), + {ok, _} = net_kernel:start(list_to_atom(NodeName), + DynStartOpts), + true = erlang:is_alive(), + true = nonode@nohost /= node(), + lists:foreach(CheckET, ETs), + call_local_success(CPort, ETs), + ok = net_kernel:stop(), + false = erlang:is_alive(), + nonode@nohost = node(), + lists:foreach(CheckET, ETs), + call_local_success(CPort, ETs), + {ok, ExtList} = file:consult(FileName), + lists:foreach(fun (Ext) when is_binary(Ext) -> + _ = binary_to_term(Ext), + SPort ! {self(), {command, Ext}}, + receive + {SPort, "bad_term_error"} -> + error(bad_term_error); + {SPort, _} -> + ok + end + end, + ExtList), + true = port_close(SPort), + true = port_close(CPort), + ok + end, + ok = peer:call(Peer1, erlang, apply, [EncDecLocal, []]), + DecOthersLocal = fun () -> + %% Verify that decoding of the terms encoded + %% on local external format by the other runtime + %% system instance fails on this runtime system + %% instance... + erl_ddll:start(), + ok = erl_ddll:load_driver(DataDir, SDrv), + SPort = open_port({spawn, SDrv}, []), + ok = erl_ddll:load_driver(DataDir, CDrv), + CPort = open_port({spawn, CDrv}, []), + false = erlang:is_alive(), + nonode@nohost = node(), + {ok, ExtList} = file:consult(FileName), + lists:foreach(fun (Ext) when is_binary(Ext) -> + try + Term = binary_to_term(Ext), + error({successful_decode, Term}) + catch + error:badarg -> + ok + end, + SPort ! {self(), {command, Ext}}, + receive + {SPort, Reply} -> + "bad_term_error" = Reply + end + end, + ExtList), + call_local_fail(CPort, ExtList), + true = port_close(SPort), + true = port_close(CPort), + ok + end, + ok = peer:call(Peer2, erlang, apply, [DecOthersLocal, []]), + peer:stop(Peer1), + peer:stop(Peer2), + ok. + +enc_local(FD, Term) -> + Ext = term_to_binary(Term, [local]), + Ext = iolist_to_binary(term_to_iovec(Term, [local])), + Term = binary_to_term(Ext), + io:format(FD, "~p.~n", [Ext]), + Ext. + +call_local_success(Port, []) -> + ok; +call_local_success(Port, [{Lext1, T1}]) -> + Me = self(), + Ref = make_ref(), + Term = {term_to_binary(Me), Lext1, term_to_binary(Ref)}, + {call_result, Me, 4711, T1, 17, Ref, "end_of_data"} = erlang:port_call(Port, 0, Term), + ok; +call_local_success(Port, [{Lext1, T1}, {Lext3, T3} | Rest]) -> + Me = self(), + Term = {Lext1, term_to_binary(Me), Lext3}, + {call_result, T1, 4711, Me, 17, T3, "end_of_data"} = erlang:port_call(Port, 0, Term), + call_local_success(Port, Rest). + +call_local_fail(Port, []) -> + ok; +call_local_fail(Port, [Lext1]) -> + Me = self(), + Ref = make_ref(), + Term = {term_to_binary(Me), Lext1, term_to_binary(Ref)}, + try + erlang:port_call(Port, 0, Term), + error(unexpected_port_call_success) + catch + error:badarg -> + ok + end; +call_local_fail(Port, [Lext1, Lext3 | Rest]) -> + Me = self(), + Term = {Lext1, term_to_binary(Me), Lext3}, + try + erlang:port_call(Port, 0, Term), + error(unexpected_port_call_success) + catch + error:badarg -> + ok + end, + call_local_fail(Port, Rest). diff --git a/erts/emulator/test/binary_SUITE_data/Makefile.src b/erts/emulator/test/binary_SUITE_data/Makefile.src new file mode 100644 index 0000000000..541dd6c1ad --- /dev/null +++ b/erts/emulator/test/binary_SUITE_data/Makefile.src @@ -0,0 +1,33 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2023. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# + +include @erl_interface_mk_include@ + +CC = @CC@ +LD = @LD@ +CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@ +CROSSLDFLAGS = @CROSSLDFLAGS@ + +SHLIB_EXTRA_CFLAGS = @EI_CFLAGS@ -I@erl_interface_include@ +SHLIB_EXTRA_LDLIBS = @erl_interface_eilib@ + +all: send_term_local_drv@dll@ call_local_drv@dll@ + +@SHLIB_RULES@ diff --git a/erts/emulator/test/binary_SUITE_data/call_local_drv.c b/erts/emulator/test/binary_SUITE_data/call_local_drv.c new file mode 100644 index 0000000000..8db6c1c772 --- /dev/null +++ b/erts/emulator/test/binary_SUITE_data/call_local_drv.c @@ -0,0 +1,204 @@ +/* ``Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * The Initial Developer of the Original Code is Ericsson Utvecklings AB. + * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings + * AB. All Rights Reserved.'' + * + * $Id$ + */ + +#include <stdio.h> +#include <string.h> +#include "erl_driver.h" +#include "ei.h" + +static ErlDrvSSizeT call(ErlDrvData drv_data, + unsigned int command, + char *buf, ErlDrvSizeT len, + char **rbuf, ErlDrvSizeT rlen, + unsigned int *flags); + +static ErlDrvEntry call_local_drv_entry = { + NULL /* init */, + NULL /* start */, + NULL /* stop */, + NULL /* output */, + NULL /* ready_input */, + NULL /* ready_output */, + "call_local_drv", + NULL /* finish */, + NULL /* handle */, + NULL /* control */, + NULL /* timeout */, + NULL /* outputv */, + NULL /* ready_async */, + NULL /* flush */, + call, + NULL /* event */, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL /* handle2 */, + NULL /* handle_monitor */ +}; + +DRIVER_INIT(call_local_drv) +{ + return &call_local_drv_entry; +} + + +static ErlDrvSSizeT call(ErlDrvData drv_data, + unsigned int command, + char *buf, ErlDrvSizeT len, + char **rbuf, ErlDrvSizeT rlen, + unsigned int *flags) +{ + ei_x_buff xbuf; + void *bin1 = NULL, *bin2 = NULL, *bin3 = NULL; + char *lext1, *lext2, *lext3; + int vsn, arity, type, ix, lix, res, err, size; + long size1, size2, size3; + ErlDrvSSizeT ret_size = (ErlDrvSSizeT) ERL_DRV_ERROR_GENERAL; + + xbuf.buff = NULL; + + ei_init(); + + ix = 0; + res = ei_decode_version(buf, &ix, &vsn); + if (res != 0 || vsn != 131) + goto error; + + res = ei_decode_tuple_header(buf, &ix, &arity); + if (res != 0) + goto error; + + /* External term 1 */ + res = ei_get_type(buf, &ix, &type, &size); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + size1 = size; + bin1 = driver_alloc(size1); + + res = ei_decode_binary(buf, &ix, bin1, &size1); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + lext1 = bin1; + lix = 0; + res = ei_decode_version(lext1, &lix, &vsn); + if (res != 0 || vsn != 131) + goto error; + lext1 += lix; + size1 -= lix; + + /* External term 2 */ + res = ei_get_type(buf, &ix, &type, &size); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + size2 = size; + bin2 = driver_alloc(size2); + + res = ei_decode_binary(buf, &ix, bin2, &size2); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + lext2 = bin2; + lix = 0; + res = ei_decode_version(lext2, &lix, &vsn); + if (res != 0 || vsn != 131) + goto error; + lext2 += lix; + size2 -= lix; + + /* External term 3 */ + res = ei_get_type(buf, &ix, &type, &size); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + size3 = size; + bin3 = driver_alloc(size3); + + res = ei_decode_binary(buf, &ix, bin3, &size3); + if (res != 0 && type != ERL_BINARY_EXT) + goto error; + + lext3 = bin3; + lix = 0; + res = ei_decode_version(lext3, &lix, &vsn); + if (res != 0 || vsn != 131) + goto error; + lext3 += lix; + size3 -= lix; + + /* encode result */ + + res = ei_x_new_with_version(&xbuf); + if (res != 0) + goto error; + + res = ei_x_encode_tuple_header(&xbuf, 7); + if (res != 0) + goto error; + + res = ei_x_encode_atom(&xbuf, "call_result"); + if (res != 0) + goto error; + + res = ei_x_append_buf(&xbuf, lext1, size1); + if (res != 0) + goto error; + + res = ei_x_encode_long(&xbuf, 4711); + if (res != 0) + goto error; + + res = ei_x_append_buf(&xbuf, lext2, size2); + if (res != 0) + goto error; + + res = ei_x_encode_long(&xbuf, 17); + if (res != 0) + goto error; + + res = ei_x_append_buf(&xbuf, lext3, size3); + if (res != 0) + goto error; + + res = ei_x_encode_string(&xbuf, "end_of_data"); + if (res != 0) + goto error; + + /* success */ + ret_size = xbuf.index; + *rbuf = driver_alloc(ret_size); + memcpy((void *) *rbuf, (void *) xbuf.buff, ret_size); + +error: + + if (bin1) + driver_free(bin1); + if (bin2) + driver_free(bin2); + if (bin3) + driver_free(bin3); + + if (xbuf.buff) + ei_x_free(&xbuf); + + return ret_size; +} diff --git a/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c b/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c new file mode 100644 index 0000000000..a5335d4a09 --- /dev/null +++ b/erts/emulator/test/binary_SUITE_data/send_term_local_drv.c @@ -0,0 +1,96 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2023. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#include "erl_driver.h" +#include <string.h> + +static void stop(ErlDrvData drv_data); +static ErlDrvData start(ErlDrvPort port, + char *command); +static void output(ErlDrvData drv_data, + char *buf, ErlDrvSizeT len); + +static ErlDrvEntry send_term_local_drv_entry = { + NULL /* init */, + start, + stop, + output, + NULL /* ready_input */, + NULL /* ready_output */, + "send_term_local_drv", + NULL /* finish */, + NULL /* handle */, + NULL /* control */, + NULL /* timeout */, + NULL /* outputv */, + NULL /* ready_async */, + NULL /* flush */, + NULL /* call */, + NULL /* event */, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL /* handle2 */, + NULL /* handle_monitor */ +}; + +DRIVER_INIT(send_term_local_drv) +{ + return &send_term_local_drv_entry; +} + +static void stop(ErlDrvData drv_data) +{ +} + +static ErlDrvData start(ErlDrvPort port, + char *command) +{ + + return (ErlDrvData) port; +} + +static void output(ErlDrvData drv_data, + char *buf, ErlDrvSizeT len) +{ + ErlDrvPort port = (ErlDrvPort) drv_data; + ErlDrvTermData term_port = driver_mk_port(port); + ErlDrvTermData caller = driver_caller(port); + int res; + ErlDrvTermData spec[] = { + ERL_DRV_PORT, term_port, + ERL_DRV_EXT2TERM, (ErlDrvTermData) buf, len, + ERL_DRV_TUPLE, 2 + }; + if (0 >= erl_drv_send_term(term_port, caller, + spec, sizeof(spec)/sizeof(spec[0]))) { + char *bad_term = "bad_term_error"; + ErlDrvTermData spec[] = { + ERL_DRV_PORT, term_port, + ERL_DRV_STRING, (ErlDrvTermData) bad_term, strlen(bad_term), + ERL_DRV_TUPLE, 2 + }; + if (0 >= erl_drv_send_term(term_port, caller, spec, + sizeof(spec)/sizeof(spec[0]))) { + driver_failure_atom(port, "failed_to_bad_term_error"); + } + } +} diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 8fd8a847dd..54cbf998c0 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 34bae47421..923b0ad806 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -974,7 +974,11 @@ external_size(_Term) -> %% external_size/2 -spec erlang:external_size(Term, Options) -> non_neg_integer() when Term :: term(), - Options :: [{minor_version, Version :: non_neg_integer()}]. + Options :: [compressed | + {compressed, Level :: 0..9} | + deterministic | + {minor_version, Version :: 0..2} | + local ]. external_size(_Term, _Options) -> erlang:nif_error(undefined). @@ -2768,7 +2772,8 @@ term_to_binary(_Term) -> Options :: [compressed | {compressed, Level :: 0..9} | deterministic | - {minor_version, Version :: 0..2} ]. + {minor_version, Version :: 0..2} | + local ]. term_to_binary(_Term, _Options) -> erlang:nif_error(undefined). @@ -2782,7 +2787,8 @@ term_to_iovec(_Term) -> Options :: [compressed | {compressed, Level :: 0..9} | deterministic | - {minor_version, Version :: 0..2} ]. + {minor_version, Version :: 0..2} | + local ]. term_to_iovec(_Term, _Options) -> erlang:nif_error(undefined). diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c index c39851ba49..de46a22c0a 100644 --- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c +++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c @@ -19,6 +19,7 @@ */ #include "ei_runner.h" +#include <string.h> /* * Purpose: Read pids, funs and others without real meaning on the C side @@ -291,6 +292,7 @@ void decode_encode(struct Type** tv, int nobj) ei_x_new(&arg); for (i=0; i<nobj; i++) { struct Type* t = tv[i]; + int small_port = 0; MESSAGE("ei_decode_%s, arg is type %s", t->name, t->type); @@ -355,9 +357,17 @@ void decode_encode(struct Type** tv, int nobj) } } if (size1 != size2) { - MESSAGE("size1 = %d, size2 = %d\n",size1,size2); - fail("decode and encode size differs when buf is NULL"); - return; + if (strcmp(t->type, "erlang_port") == 0 + && size1 == size2 + 4 + && objv[oix].u.port.id <= 0x0fffffff /* 28 bits */) { + /* old encoding... */ + small_port = !0; + } + else { + MESSAGE("size1 = %d, size2 = %d\n",size1,size2); + fail("decode and encode size differs when buf is NULL"); + return; + } } MESSAGE("ei_encode_%s, arg is type %s", t->name, t->type); size3 = 0; @@ -371,9 +381,11 @@ void decode_encode(struct Type** tv, int nobj) return; } if (size1 != size3) { - MESSAGE("size1 = %d, size2 = %d\n",size1,size3); - fail("decode and encode size differs"); - return; + if (!small_port || size2 != size3) { + MESSAGE("size1 = %d, size3 = %d\n",size1,size3); + fail("decode and encode size differs"); + return; + } } MESSAGE("ei_x_encode_%s, arg is type %s", t->name, t->type); @@ -394,7 +406,7 @@ void decode_encode(struct Type** tv, int nobj) } inp += size1; - outp += size1; + outp += size2; if (objv[oix].nterms) { /* container term */ if (++oix >= sizeof(objv)/sizeof(*objv)) |
