1*e0c4386eSCy Schubert /*
2*e0c4386eSCy Schubert * Copyright 2018-2021 The OpenSSL Project Authors. All Rights Reserved.
3*e0c4386eSCy Schubert *
4*e0c4386eSCy Schubert * Licensed under the Apache License 2.0 (the "License"). You may not use
5*e0c4386eSCy Schubert * this file except in compliance with the License. You can obtain a copy
6*e0c4386eSCy Schubert * in the file LICENSE in the source distribution or at
7*e0c4386eSCy Schubert * https://www.openssl.org/source/license.html
8*e0c4386eSCy Schubert */
9*e0c4386eSCy Schubert
10*e0c4386eSCy Schubert #include <string.h>
11*e0c4386eSCy Schubert #include <openssl/opensslconf.h>
12*e0c4386eSCy Schubert #include <openssl/err.h>
13*e0c4386eSCy Schubert #include <openssl/macros.h>
14*e0c4386eSCy Schubert
15*e0c4386eSCy Schubert #include "testutil.h"
16*e0c4386eSCy Schubert
17*e0c4386eSCy Schubert #if defined(OPENSSL_SYS_WINDOWS)
18*e0c4386eSCy Schubert # include <windows.h>
19*e0c4386eSCy Schubert #else
20*e0c4386eSCy Schubert # include <errno.h>
21*e0c4386eSCy Schubert #endif
22*e0c4386eSCy Schubert
23*e0c4386eSCy Schubert #ifndef OPENSSL_NO_DEPRECATED_3_0
24*e0c4386eSCy Schubert # define IS_HEX(ch) ((ch >= '0' && ch <='9') || (ch >= 'A' && ch <='F'))
25*e0c4386eSCy Schubert
test_print_error_format(void)26*e0c4386eSCy Schubert static int test_print_error_format(void)
27*e0c4386eSCy Schubert {
28*e0c4386eSCy Schubert /* Variables used to construct an error line */
29*e0c4386eSCy Schubert char *lib;
30*e0c4386eSCy Schubert const char *func = OPENSSL_FUNC;
31*e0c4386eSCy Schubert char *reason;
32*e0c4386eSCy Schubert # ifdef OPENSSL_NO_ERR
33*e0c4386eSCy Schubert char reasonbuf[255];
34*e0c4386eSCy Schubert # endif
35*e0c4386eSCy Schubert # ifndef OPENSSL_NO_FILENAMES
36*e0c4386eSCy Schubert const char *file = OPENSSL_FILE;
37*e0c4386eSCy Schubert const int line = OPENSSL_LINE;
38*e0c4386eSCy Schubert # else
39*e0c4386eSCy Schubert const char *file = "";
40*e0c4386eSCy Schubert const int line = 0;
41*e0c4386eSCy Schubert # endif
42*e0c4386eSCy Schubert /* The format for OpenSSL error lines */
43*e0c4386eSCy Schubert const char *expected_format = ":error:%08lX:%s:%s:%s:%s:%d";
44*e0c4386eSCy Schubert /*-
45*e0c4386eSCy Schubert * ^^ ^^ ^^ ^^ ^^
46*e0c4386eSCy Schubert * "library" name --------------------------++ || || || ||
47*e0c4386eSCy Schubert * function name ------------------------------++ || || ||
48*e0c4386eSCy Schubert * reason string (system error string) -----------++ || ||
49*e0c4386eSCy Schubert * file name ----------------------------------------++ ||
50*e0c4386eSCy Schubert * line number -----------------------------------------++
51*e0c4386eSCy Schubert */
52*e0c4386eSCy Schubert char expected[512];
53*e0c4386eSCy Schubert
54*e0c4386eSCy Schubert char *out = NULL, *p = NULL;
55*e0c4386eSCy Schubert int ret = 0, len;
56*e0c4386eSCy Schubert BIO *bio = NULL;
57*e0c4386eSCy Schubert const int syserr = EPERM;
58*e0c4386eSCy Schubert unsigned long errorcode;
59*e0c4386eSCy Schubert unsigned long reasoncode;
60*e0c4386eSCy Schubert
61*e0c4386eSCy Schubert /*
62*e0c4386eSCy Schubert * We set a mark here so we can clear the system error that we generate
63*e0c4386eSCy Schubert * with ERR_PUT_error(). That is, after all, just a simulation to verify
64*e0c4386eSCy Schubert * ERR_print_errors() output, not a real error.
65*e0c4386eSCy Schubert */
66*e0c4386eSCy Schubert ERR_set_mark();
67*e0c4386eSCy Schubert
68*e0c4386eSCy Schubert ERR_PUT_error(ERR_LIB_SYS, 0, syserr, file, line);
69*e0c4386eSCy Schubert errorcode = ERR_peek_error();
70*e0c4386eSCy Schubert reasoncode = ERR_GET_REASON(errorcode);
71*e0c4386eSCy Schubert
72*e0c4386eSCy Schubert if (!TEST_int_eq(reasoncode, syserr)) {
73*e0c4386eSCy Schubert ERR_pop_to_mark();
74*e0c4386eSCy Schubert goto err;
75*e0c4386eSCy Schubert }
76*e0c4386eSCy Schubert
77*e0c4386eSCy Schubert # if !defined(OPENSSL_NO_ERR)
78*e0c4386eSCy Schubert # if defined(OPENSSL_NO_AUTOERRINIT)
79*e0c4386eSCy Schubert lib = "lib(2)";
80*e0c4386eSCy Schubert # else
81*e0c4386eSCy Schubert lib = "system library";
82*e0c4386eSCy Schubert # endif
83*e0c4386eSCy Schubert reason = strerror(syserr);
84*e0c4386eSCy Schubert # else
85*e0c4386eSCy Schubert lib = "lib(2)";
86*e0c4386eSCy Schubert BIO_snprintf(reasonbuf, sizeof(reasonbuf), "reason(%lu)", reasoncode);
87*e0c4386eSCy Schubert reason = reasonbuf;
88*e0c4386eSCy Schubert # endif
89*e0c4386eSCy Schubert
90*e0c4386eSCy Schubert BIO_snprintf(expected, sizeof(expected), expected_format,
91*e0c4386eSCy Schubert errorcode, lib, func, reason, file, line);
92*e0c4386eSCy Schubert
93*e0c4386eSCy Schubert if (!TEST_ptr(bio = BIO_new(BIO_s_mem())))
94*e0c4386eSCy Schubert goto err;
95*e0c4386eSCy Schubert
96*e0c4386eSCy Schubert ERR_print_errors(bio);
97*e0c4386eSCy Schubert
98*e0c4386eSCy Schubert if (!TEST_int_gt(len = BIO_get_mem_data(bio, &out), 0))
99*e0c4386eSCy Schubert goto err;
100*e0c4386eSCy Schubert /* Skip over the variable thread id at the start of the string */
101*e0c4386eSCy Schubert for (p = out; *p != ':' && *p != 0; ++p) {
102*e0c4386eSCy Schubert if (!TEST_true(IS_HEX(*p)))
103*e0c4386eSCy Schubert goto err;
104*e0c4386eSCy Schubert }
105*e0c4386eSCy Schubert if (!TEST_true(*p != 0)
106*e0c4386eSCy Schubert || !TEST_strn_eq(expected, p, strlen(expected)))
107*e0c4386eSCy Schubert goto err;
108*e0c4386eSCy Schubert
109*e0c4386eSCy Schubert ret = 1;
110*e0c4386eSCy Schubert err:
111*e0c4386eSCy Schubert BIO_free(bio);
112*e0c4386eSCy Schubert return ret;
113*e0c4386eSCy Schubert }
114*e0c4386eSCy Schubert #endif
115*e0c4386eSCy Schubert
116*e0c4386eSCy Schubert /* Test that querying the error queue preserves the OS error. */
preserves_system_error(void)117*e0c4386eSCy Schubert static int preserves_system_error(void)
118*e0c4386eSCy Schubert {
119*e0c4386eSCy Schubert #if defined(OPENSSL_SYS_WINDOWS)
120*e0c4386eSCy Schubert SetLastError(ERROR_INVALID_FUNCTION);
121*e0c4386eSCy Schubert ERR_get_error();
122*e0c4386eSCy Schubert return TEST_int_eq(GetLastError(), ERROR_INVALID_FUNCTION);
123*e0c4386eSCy Schubert #else
124*e0c4386eSCy Schubert errno = EINVAL;
125*e0c4386eSCy Schubert ERR_get_error();
126*e0c4386eSCy Schubert return TEST_int_eq(errno, EINVAL);
127*e0c4386eSCy Schubert #endif
128*e0c4386eSCy Schubert }
129*e0c4386eSCy Schubert
130*e0c4386eSCy Schubert /* Test that calls to ERR_add_error_[v]data append */
vdata_appends(void)131*e0c4386eSCy Schubert static int vdata_appends(void)
132*e0c4386eSCy Schubert {
133*e0c4386eSCy Schubert const char *data;
134*e0c4386eSCy Schubert
135*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
136*e0c4386eSCy Schubert ERR_add_error_data(1, "hello ");
137*e0c4386eSCy Schubert ERR_add_error_data(1, "world");
138*e0c4386eSCy Schubert ERR_peek_error_data(&data, NULL);
139*e0c4386eSCy Schubert return TEST_str_eq(data, "hello world");
140*e0c4386eSCy Schubert }
141*e0c4386eSCy Schubert
raised_error(void)142*e0c4386eSCy Schubert static int raised_error(void)
143*e0c4386eSCy Schubert {
144*e0c4386eSCy Schubert const char *f, *data;
145*e0c4386eSCy Schubert int l;
146*e0c4386eSCy Schubert unsigned long e;
147*e0c4386eSCy Schubert
148*e0c4386eSCy Schubert /*
149*e0c4386eSCy Schubert * When OPENSSL_NO_ERR or OPENSSL_NO_FILENAMES, no file name or line
150*e0c4386eSCy Schubert * number is saved, so no point checking them.
151*e0c4386eSCy Schubert */
152*e0c4386eSCy Schubert #if !defined(OPENSSL_NO_FILENAMES) && !defined(OPENSSL_NO_ERR)
153*e0c4386eSCy Schubert const char *file;
154*e0c4386eSCy Schubert int line;
155*e0c4386eSCy Schubert
156*e0c4386eSCy Schubert file = __FILE__;
157*e0c4386eSCy Schubert line = __LINE__ + 2; /* The error is generated on the ERR_raise_data line */
158*e0c4386eSCy Schubert #endif
159*e0c4386eSCy Schubert ERR_raise_data(ERR_LIB_NONE, ERR_R_INTERNAL_ERROR,
160*e0c4386eSCy Schubert "calling exit()");
161*e0c4386eSCy Schubert if (!TEST_ulong_ne(e = ERR_get_error_all(&f, &l, NULL, &data, NULL), 0)
162*e0c4386eSCy Schubert || !TEST_int_eq(ERR_GET_REASON(e), ERR_R_INTERNAL_ERROR)
163*e0c4386eSCy Schubert #if !defined(OPENSSL_NO_FILENAMES) && !defined(OPENSSL_NO_ERR)
164*e0c4386eSCy Schubert || !TEST_int_eq(l, line)
165*e0c4386eSCy Schubert || !TEST_str_eq(f, file)
166*e0c4386eSCy Schubert #endif
167*e0c4386eSCy Schubert || !TEST_str_eq(data, "calling exit()"))
168*e0c4386eSCy Schubert return 0;
169*e0c4386eSCy Schubert return 1;
170*e0c4386eSCy Schubert }
171*e0c4386eSCy Schubert
test_marks(void)172*e0c4386eSCy Schubert static int test_marks(void)
173*e0c4386eSCy Schubert {
174*e0c4386eSCy Schubert unsigned long mallocfail, shouldnot;
175*e0c4386eSCy Schubert
176*e0c4386eSCy Schubert /* Set an initial error */
177*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
178*e0c4386eSCy Schubert mallocfail = ERR_peek_last_error();
179*e0c4386eSCy Schubert if (!TEST_ulong_gt(mallocfail, 0))
180*e0c4386eSCy Schubert return 0;
181*e0c4386eSCy Schubert
182*e0c4386eSCy Schubert /* Setting and clearing a mark should not affect the error */
183*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark())
184*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
185*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error())
186*e0c4386eSCy Schubert || !TEST_true(ERR_set_mark())
187*e0c4386eSCy Schubert || !TEST_true(ERR_clear_last_mark())
188*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error()))
189*e0c4386eSCy Schubert return 0;
190*e0c4386eSCy Schubert
191*e0c4386eSCy Schubert /* Test popping errors */
192*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark()))
193*e0c4386eSCy Schubert return 0;
194*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
195*e0c4386eSCy Schubert if (!TEST_ulong_ne(mallocfail, ERR_peek_last_error())
196*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
197*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error()))
198*e0c4386eSCy Schubert return 0;
199*e0c4386eSCy Schubert
200*e0c4386eSCy Schubert /* Nested marks should also work */
201*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark())
202*e0c4386eSCy Schubert || !TEST_true(ERR_set_mark()))
203*e0c4386eSCy Schubert return 0;
204*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
205*e0c4386eSCy Schubert if (!TEST_ulong_ne(mallocfail, ERR_peek_last_error())
206*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
207*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
208*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error()))
209*e0c4386eSCy Schubert return 0;
210*e0c4386eSCy Schubert
211*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark()))
212*e0c4386eSCy Schubert return 0;
213*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
214*e0c4386eSCy Schubert shouldnot = ERR_peek_last_error();
215*e0c4386eSCy Schubert if (!TEST_ulong_ne(mallocfail, shouldnot)
216*e0c4386eSCy Schubert || !TEST_true(ERR_set_mark()))
217*e0c4386eSCy Schubert return 0;
218*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
219*e0c4386eSCy Schubert if (!TEST_ulong_ne(shouldnot, ERR_peek_last_error())
220*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
221*e0c4386eSCy Schubert || !TEST_ulong_eq(shouldnot, ERR_peek_last_error())
222*e0c4386eSCy Schubert || !TEST_true(ERR_pop_to_mark())
223*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error()))
224*e0c4386eSCy Schubert return 0;
225*e0c4386eSCy Schubert
226*e0c4386eSCy Schubert /* Setting and clearing a mark should not affect the errors on the stack */
227*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark()))
228*e0c4386eSCy Schubert return 0;
229*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
230*e0c4386eSCy Schubert if (!TEST_true(ERR_clear_last_mark())
231*e0c4386eSCy Schubert || !TEST_ulong_eq(shouldnot, ERR_peek_last_error()))
232*e0c4386eSCy Schubert return 0;
233*e0c4386eSCy Schubert
234*e0c4386eSCy Schubert /*
235*e0c4386eSCy Schubert * Popping where no mark has been set should pop everything - but return
236*e0c4386eSCy Schubert * a failure result
237*e0c4386eSCy Schubert */
238*e0c4386eSCy Schubert if (!TEST_false(ERR_pop_to_mark())
239*e0c4386eSCy Schubert || !TEST_ulong_eq(0, ERR_peek_last_error()))
240*e0c4386eSCy Schubert return 0;
241*e0c4386eSCy Schubert
242*e0c4386eSCy Schubert /* Clearing where there is no mark should fail */
243*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
244*e0c4386eSCy Schubert if (!TEST_false(ERR_clear_last_mark())
245*e0c4386eSCy Schubert /* "get" the last error to remove it */
246*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_get_error())
247*e0c4386eSCy Schubert || !TEST_ulong_eq(0, ERR_peek_last_error()))
248*e0c4386eSCy Schubert return 0;
249*e0c4386eSCy Schubert
250*e0c4386eSCy Schubert /*
251*e0c4386eSCy Schubert * Setting a mark where there are no errors in the stack should fail.
252*e0c4386eSCy Schubert * NOTE: This is somewhat surprising behaviour but is historically how this
253*e0c4386eSCy Schubert * function behaves. In practice we typically set marks without first
254*e0c4386eSCy Schubert * checking whether there is anything on the stack - but we also don't
255*e0c4386eSCy Schubert * tend to check the success of this function. It turns out to work anyway
256*e0c4386eSCy Schubert * because although setting a mark with no errors fails, a subsequent call
257*e0c4386eSCy Schubert * to ERR_pop_to_mark() or ERR_clear_last_mark() will do the right thing
258*e0c4386eSCy Schubert * anyway (even though they will report a failure result).
259*e0c4386eSCy Schubert */
260*e0c4386eSCy Schubert if (!TEST_false(ERR_set_mark()))
261*e0c4386eSCy Schubert return 0;
262*e0c4386eSCy Schubert
263*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_MALLOC_FAILURE);
264*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark()))
265*e0c4386eSCy Schubert return 0;
266*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
267*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
268*e0c4386eSCy Schubert
269*e0c4386eSCy Schubert /* Should be able to "pop" past 2 errors */
270*e0c4386eSCy Schubert if (!TEST_true(ERR_pop_to_mark())
271*e0c4386eSCy Schubert || !TEST_ulong_eq(mallocfail, ERR_peek_last_error()))
272*e0c4386eSCy Schubert return 0;
273*e0c4386eSCy Schubert
274*e0c4386eSCy Schubert if (!TEST_true(ERR_set_mark()))
275*e0c4386eSCy Schubert return 0;
276*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_INTERNAL_ERROR);
277*e0c4386eSCy Schubert ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
278*e0c4386eSCy Schubert
279*e0c4386eSCy Schubert /* Should be able to "clear" past 2 errors */
280*e0c4386eSCy Schubert if (!TEST_true(ERR_clear_last_mark())
281*e0c4386eSCy Schubert || !TEST_ulong_eq(shouldnot, ERR_peek_last_error()))
282*e0c4386eSCy Schubert return 0;
283*e0c4386eSCy Schubert
284*e0c4386eSCy Schubert /* Clear remaining errors from last test */
285*e0c4386eSCy Schubert ERR_clear_error();
286*e0c4386eSCy Schubert
287*e0c4386eSCy Schubert return 1;
288*e0c4386eSCy Schubert }
289*e0c4386eSCy Schubert
test_clear_error(void)290*e0c4386eSCy Schubert static int test_clear_error(void)
291*e0c4386eSCy Schubert {
292*e0c4386eSCy Schubert int flags = -1;
293*e0c4386eSCy Schubert const char *data = NULL;
294*e0c4386eSCy Schubert int res = 0;
295*e0c4386eSCy Schubert
296*e0c4386eSCy Schubert /* Raise an error with data and clear it */
297*e0c4386eSCy Schubert ERR_raise_data(0, 0, "hello %s", "world");
298*e0c4386eSCy Schubert ERR_peek_error_data(&data, &flags);
299*e0c4386eSCy Schubert if (!TEST_str_eq(data, "hello world")
300*e0c4386eSCy Schubert || !TEST_int_eq(flags, ERR_TXT_STRING | ERR_TXT_MALLOCED))
301*e0c4386eSCy Schubert goto err;
302*e0c4386eSCy Schubert ERR_clear_error();
303*e0c4386eSCy Schubert
304*e0c4386eSCy Schubert /* Raise a new error without data */
305*e0c4386eSCy Schubert ERR_raise(0, 0);
306*e0c4386eSCy Schubert ERR_peek_error_data(&data, &flags);
307*e0c4386eSCy Schubert if (!TEST_str_eq(data, "")
308*e0c4386eSCy Schubert || !TEST_int_eq(flags, ERR_TXT_MALLOCED))
309*e0c4386eSCy Schubert goto err;
310*e0c4386eSCy Schubert ERR_clear_error();
311*e0c4386eSCy Schubert
312*e0c4386eSCy Schubert /* Raise a new error with data */
313*e0c4386eSCy Schubert ERR_raise_data(0, 0, "goodbye %s world", "cruel");
314*e0c4386eSCy Schubert ERR_peek_error_data(&data, &flags);
315*e0c4386eSCy Schubert if (!TEST_str_eq(data, "goodbye cruel world")
316*e0c4386eSCy Schubert || !TEST_int_eq(flags, ERR_TXT_STRING | ERR_TXT_MALLOCED))
317*e0c4386eSCy Schubert goto err;
318*e0c4386eSCy Schubert ERR_clear_error();
319*e0c4386eSCy Schubert
320*e0c4386eSCy Schubert /*
321*e0c4386eSCy Schubert * Raise a new error without data to check that the malloced storage
322*e0c4386eSCy Schubert * is freed properly
323*e0c4386eSCy Schubert */
324*e0c4386eSCy Schubert ERR_raise(0, 0);
325*e0c4386eSCy Schubert ERR_peek_error_data(&data, &flags);
326*e0c4386eSCy Schubert if (!TEST_str_eq(data, "")
327*e0c4386eSCy Schubert || !TEST_int_eq(flags, ERR_TXT_MALLOCED))
328*e0c4386eSCy Schubert goto err;
329*e0c4386eSCy Schubert ERR_clear_error();
330*e0c4386eSCy Schubert
331*e0c4386eSCy Schubert res = 1;
332*e0c4386eSCy Schubert err:
333*e0c4386eSCy Schubert ERR_clear_error();
334*e0c4386eSCy Schubert return res;
335*e0c4386eSCy Schubert }
336*e0c4386eSCy Schubert
setup_tests(void)337*e0c4386eSCy Schubert int setup_tests(void)
338*e0c4386eSCy Schubert {
339*e0c4386eSCy Schubert ADD_TEST(preserves_system_error);
340*e0c4386eSCy Schubert ADD_TEST(vdata_appends);
341*e0c4386eSCy Schubert ADD_TEST(raised_error);
342*e0c4386eSCy Schubert #ifndef OPENSSL_NO_DEPRECATED_3_0
343*e0c4386eSCy Schubert ADD_TEST(test_print_error_format);
344*e0c4386eSCy Schubert #endif
345*e0c4386eSCy Schubert ADD_TEST(test_marks);
346*e0c4386eSCy Schubert ADD_TEST(test_clear_error);
347*e0c4386eSCy Schubert return 1;
348*e0c4386eSCy Schubert }
349