473,586 Members | 2,754 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

gets() fgets() with subdivide.c tilepack.c squarect.c

I am looking to use Ken Stephenson's CirclePack program
(http://www.math.utk.edu/~kens/) with some tiling programs written by
Cannon, Floyd, Parry. The programs I want to use are subdivide.c
tilepack.c squarect.c . They are available from
http://www.math.vt.edu/people/floyd/...re/subdiv.html.

I tried compiling them on my Redhat 9 box, eg cc subdivide.c. When I do
so I get errors like "In function `Readtilingforv ertex':
: the `gets' function is dangerous and should not be used."

I checked the FAQ and is says gets() is BAD and I should use fgets()
instead. I found an example of a code fragment to use, eg
'This is the sort of code you've got:

{
char buf[512];
gets( buf );
/* ... more code ... */
}

which will cause a buffer overrun if more than 512 characters are placed
in the buffer and randomly overwrite other memory. This can cause a
number of hard-to-trace bugs and is just a dangerous idiom.

This is also the source of the the vulnerabilities in BIND and a huge
number of other programs--by feeding the buffer the right sequence, you
can actually get it execute code buried in the buffer---though I don't
think people will use CCP4 to break into UNIX boxes.

The code to replace it is really simple:

{
char buf[512];
fgets( buf, sizeof(buf), stdin );
/* ... more code ... */
}

The header file needed for the prototypes of both functions is still
stdio.h'

So not being a c programmer I just replaced all instances of gets() with
with the above. Now I am getting different errors.
[stuart@localhos t cpack]$ cc subdivide.c
subdivide.c: In function `Readrules':
subdivide.c:160 : parse error before "sizeof"
subdivide.c:160 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:160 : parse error before ')' token
subdivide.c:167 : warning: passing arg 3 of `fgets' makes pointer from integer without a cast
subdivide.c: In function `Readtiling':
subdivide.c:311 : parse error before "sizeof"
subdivide.c:311 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:311 : parse error before ')' token
subdivide.c:318 : warning: passing arg 3 of `fgets' makes pointer from integer without a cast
subdivide.c: In function `Readtypetiling ':
subdivide.c:461 : parse error before "sizeof"
subdivide.c:461 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:461 : parse error before ')' token
subdivide.c: In function `Readbdytiling' :
subdivide.c:596 : parse error before "sizeof"
subdivide.c:596 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:596 : parse error before ')' token
subdivide.c: In function `Subdivide':
subdivide.c:719 : parse error before "sizeof"
subdivide.c:719 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:719 : parse error before ')' token
subdivide.c: In function `Writetilingtof ile':
subdivide.c:972 : parse error before "sizeof"
subdivide.c:972 : conflicting types for `stdin'
/usr/include/stdio.h:142: previous declaration of `stdin'
subdivide.c:972 : parse error before ')' token
subdivide.c:979 : warning: passing arg 3 of `fgets' makes pointer from integer without a cast

Any ideas?

thanks ,

Stuart

Nov 14 '05 #1
10 3720

On Wed, 21 Jan 2004, Stuart Anderson wrote:

I am looking to use Ken Stephenson's CirclePack program
(http://www.math.utk.edu/~kens/) with some tiling programs written by
Cannon, Floyd, Parry. The programs I want to use are subdivide.c
tilepack.c squarect.c . They are available from
http://www.math.vt.edu/people/floyd/...re/subdiv.html.
Yuck, that's old code! I really wish science-type people would
learn the modern language; it would make life a lot easier for people
like you and me. Unfortunately, a lot of algorithms today are still
being presented in the programming equivalent of Shakespearean English,
a.k.a. "K&R C." A long quote from subdivide.c:

Readrules()
{ /* loop variables */
int i,j,k;

/* standard input variable and functions */
char s[256];
extern char *gets();
extern int atoi();

FILE *fp; /* move this to the variable declaration section */
fprintf(stderr, " Read which rules file? (e.g., filename.r)\n") ;
gets(s);
if ((fp = fopen(s,"r")) == NULL)
{
fprintf(stderr, " cannot open file \n");
exit();
}
[...]
In modern standard C, this would be written something like this:

#include <stdio.h>
#include <stdlib.h>

int Readrules(void)
{
int i, j, k;
char s[256];
FILE *fp;

fprintf(stderr, " Read which rules file? (e.g., filename.r)\n") ;
fgets(s, sizeof s, stdin);
if ((fp = fopen(s, "r")) == NULL)
{
fprintf(stderr, " cannot open file \n");
exit(EXIT_FAILU RE);
}
[...]

Note the correct use of 'fgets' above. This is what your code
should look like if you want 'cc' to stop giving you errors. (By
the way, 'cc' is usually an alias for the compiler usually known as
'gcc'; that's something you should know if you read this group.
And the c.l.c-recommended way to invoke 'gcc' is

gcc -W -Wall -ansi -pedantic -O2 subdivide.c

It's longer, but it's much, much better in terms of the help and
diagnostic messages it will give you.)

I tried compiling them on my Redhat 9 box, eg cc subdivide.c. When I do
so I get errors like "In function `Readtilingforv ertex':
: the `gets' function is dangerous and should not be used."

I checked the FAQ and is says gets() is BAD and I should use fgets()
instead.
That's right. [Snipped the long FAQ quote, all of which is correct,
of course.]
So not being a c programmer I just replaced all instances of gets() with
with the above. Now I am getting different errors.
[stuart@localhos t cpack]$ cc subdivide.c
subdivide.c: In function `Readrules':
subdivide.c:160 : parse error before "sizeof"


It would have helped a whole lot if you'd actually posted at least
a few of the lines of code about which the compiler was complaining.
For example, you could have said, "Line 160 in my copy of subdivide.c
is the line I've marked below:

char s[256];
extern char *fgets(buf, sizeof buf, stdin); /** THIS LINE **/
extern int atoi();

", and then we could have told you that you're not supposed to do that.
That's my best guess as to what these error messages mean; since you
haven't told us what you tried to compile, I really have no way of
knowing whether my diagnosis is right or not.

[snipped a lot more useless error messages]

See if you can figure out how my "non-Shakespearean" sample code
using fgets() works, and then modify your code accordingly. If you
still have trouble, post the smallest complete, compilable program
that still exhibits the bug, and we'll take a look.

-Arthur

Nov 14 '05 #2
"Arthur J. O'Dwyer" <aj*@nospam.and rew.cmu.edu> writes:
On Wed, 21 Jan 2004, Stuart Anderson wrote: [snip] In modern standard C, this would be written something like this:

#include <stdio.h>
#include <stdlib.h>

int Readrules(void)
{
int i, j, k;
char s[256];
FILE *fp;

fprintf(stderr, " Read which rules file? (e.g., filename.r)\n") ;
fgets(s, sizeof s, stdin);
if ((fp = fopen(s, "r")) == NULL)
{
fprintf(stderr, " cannot open file \n");
exit(EXIT_FAILU RE);
}
[...]

Note the correct use of 'fgets' above. This is what your code
should look like if you want 'cc' to stop giving you errors.
Apart from being far safer than gets(), fgets() also leaves the
trailing newline in the input string (unless it runs out of room
before it sees the newline). In the above code, if the user types
"filename.r " (without the quotes), the program will try to open a file
called "filename.r \n", which is unlikely to exist.

Try something like this (minimally tested):

size_t last;
...
fgets(s, sizeof s, stdin);
last = strlen(s) - 1;
if (s[last] == '\n') {
s[last] = '\0';
}
(By the way, 'cc' is usually an alias for the compiler usually known
as 'gcc'; that's something you should know if you read this group.


That's often true on Linux systems, but it's not true in general.

[snip]

--
Keith Thompson (The_Other_Keit h) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://www.sdsc.edu/~kst>
Schroedinger does Shakespeare: "To be *and* not to be"
Nov 14 '05 #3

On Wed, 21 Jan 2004, Keith Thompson wrote:

"Arthur J. O'Dwyer" <aj*@nospam.and rew.cmu.edu> writes:

fprintf(stderr, " Read which rules file? (e.g., filename.r)\n") ;
fgets(s, sizeof s, stdin);
if ((fp = fopen(s, "r")) == NULL)
{
fprintf(stderr, " cannot open file \n");
exit(EXIT_FAILU RE);
}
[...]

Note the correct use of 'fgets' above.
The best way to find your own mistakes is to confidently assert
in a public forum that you haven't made any. ;-)
Apart from being far safer than gets(), fgets() also leaves the
trailing newline in the input string (unless it runs out of room
before it sees the newline). In the above code, if the user types
"filename.r " (without the quotes), the program will try to open a file
called "filename.r \n", which is unlikely to exist.
Indeed. Whoops.
Try something like this (minimally tested):

size_t last;
...
fgets(s, sizeof s, stdin);
last = strlen(s) - 1;
if (s[last] == '\n') {
s[last] = '\0';
}


pete posted a complete fgets() example back in the mists of
<3C***********@ mindspring.com> , but it is terribly ugly, IMNSHO. :)
Note that your version will still crash on end-of-input, and as
long as you're going to be doing an O(n) algorithm anyway, you
might as well use the slightly-more-canonical version:

char *p;

p = fgets(s, sizeof s, stdin);
if (p == NULL || feof(stdin)) {
fprintf(stderr, " end of input encountered \n");
exit(EXIT_FAILU RE);
}
p = strchr(s, '\n');
if (p != NULL)
*p = '\0';
[...]

[with a few more error-checking bits added just for kicks].
Anyway, the OP should be observing that this sort of input
validation is not for the faint of heart.
I am personally a fan of 'scanf', but even the 'scanf'
solution is a bit messy in this case:

char tempfmat[20];
int rc;

sprintf(tempfma t, "%s%d%s", "%", sizeof s, "[^\n]%*[^\n]");
for (rc = 0; rc == 0; ) {
rc = scanf(tempfmat, s);
if (rc < 0) {
fprintf(stderr, " end of input encountered \n");
exit(EXIT_FAILU RE);
}
else if (rc == 0) {
fprintf(stderr, " I need a filename! \n");
}
}
[...find the bugs in this one!...]

(By the way, 'cc' is usually an alias for the compiler usually known
as 'gcc'; that's something you should know if you read this group.


That's often true on Linux systems, but it's not true in general.


Good point. I still maintain, though, that in the context of this
group, it's "usually" true. Then again, I doubt that many people in
this group actually use the 'cc' name to invoke 'gcc', so... meh.

-Arthur
Nov 14 '05 #4
Arthur J. O'Dwyer wrote:
On Wed, 21 Jan 2004, Keith Thompson wrote:
"Arthur J. O'Dwyer" <aj*@nospam.and rew.cmu.edu> writes:
fprintf(stderr, " Read which rules file? (e.g., filename.r)\n") ;
fgets(s, sizeof s, stdin);
if ((fp = fopen(s, "r")) == NULL)
{
fprintf(stderr, " cannot open file \n");
exit(EXIT_FAILU RE);
}
[...]

Note the correct use of 'fgets' above.

The best way to find your own mistakes is to confidently assert
in a public forum that you haven't made any. ;-)
I've found precisely the same thing. Over and over.

I learn, and I think egg looks good on me.
I am personally a fan of 'scanf', but even the 'scanf'
solution is a bit messy in this case:

char tempfmat[20];
int rc;

sprintf(tempfma t, "%s%d%s", "%", sizeof s, "[^\n]%*[^\n]");
for (rc = 0; rc == 0; ) {
rc = scanf(tempfmat, s);
if (rc < 0) {
fprintf(stderr, " end of input encountered \n");
exit(EXIT_FAILU RE);
}
else if (rc == 0) {
fprintf(stderr, " I need a filename! \n");
}
}
[...find the bugs in this one!...]
gcc:23:warning: scanf() is ugly and should not be used

:)

Actually, I think it's a surprisingly elegant solution given how odd
scanf is to begin with. Especially making your own format on the fly:
Very nice. :)

I would have made sure sprintf couldn't cause a buffer overrun by
mallocing the storage for the format, something that probably would have
involved calculating how many digits long the octal representation of
the number is, but I do think your solution is perfectly OK. This is
clc, not OCD.

( ... gets()... everywhere... MUST GETS()-CLEAN ... )


(By the way, 'cc' is usually an alias for the compiler usually known
as 'gcc'; that's something you should know if you read this group.


That's often true on Linux systems, but it's not true in general.

Good point. I still maintain, though, that in the context of this
group, it's "usually" true. Then again, I doubt that many people in
this group actually use the 'cc' name to invoke 'gcc', so... meh.


I do. Can't bring myself to type the extra character. (Actually, it's
muscle memory now. :) )

--
My address is yvoregnevna gjragl-guerr gjb-gubhfnaq guerr ng lnubb qbg pbz
Note: Rot13 and convert spelled-out numbers to numerical equivalents.
Nov 14 '05 #5
On Wed, 21 Jan 2004 10:12:28 +1100, Stuart Anderson wrote:

Any ideas?

thanks ,

Stuart


I think you've given me enough of an idea of what to do to get the files
to compile. I'll let you know how I go. Thanks all.

Stuart
Nov 14 '05 #6
In article <MH************ ******@fe02.use netserver.com>, se*@sig.now says...
Arthur J. O'Dwyer wrote:
Then again, I doubt that many people in
this group actually use the 'cc' name to invoke 'gcc'


I do. Can't bring myself to type the extra character. (Actually, it's
muscle memory now. :) )


Do you really type it that often? I usually invoke it indirectly, via
CC=gcc -Wall .....[some set of project-specific arguments]
in a makefile.

Several said elsewhere recently that the first thing they do on a
new project is start writing header files. For me, the Makefile
template is the first thing to get touched.

That typing stuff is for the birds. :-)

I also have a shell alias for maek='maek'. Don't ask why.

--
Randy Howard
2reply remove FOOBAR

Nov 14 '05 #7
In article <news:Pi******* *************** ************@un ix48.andrew.cmu .edu>
Arthur J. O'Dwyer <aj*@nospam.and rew.cmu.edu> writes:
I am personally a fan of 'scanf', but even the 'scanf'
solution is a bit messy in this case:

char tempfmat[20];
int rc;

sprintf(tempfma t, "%s%d%s", "%", sizeof s, "[^\n]%*[^\n]");


Actually, you need to generate the format %19[, not %20[, so you
need ((sizeof s) - 1) here; and %d takes an int while sizeof
produces a size_t (perhaps a 64-bit unsigned long, vs the 32-bit
int).

This would work:

sprintf(tempfma t, "%%%d[^\n]%*[^\n]", (int)sizeof s - 1);

or you could even use the fact that it is "obvious" (?) that 20
and 19 are "the same number" :-) and just encode %19[ directly.

As for myself, I am not such a fan of scanf(). The scanf-ish
functions in in the "sfio" package might be better (I have not
actually looked, but from the few examples I have heard of, it
seems to be aimed at interacting with those pesky "users" you may
have heard of).
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
Nov 14 '05 #8

On Wed, 21 Jan 2004, Chris Torek wrote:

Arthur J. O'Dwyer <aj*@nospam.and rew.cmu.edu> writes:
I am personally a fan of 'scanf', but even the 'scanf'
solution is a bit messy in this case:

char tempfmat[20];
int rc;

sprintf(tempfma t, "%s%d%s", "%", sizeof s, "[^\n]%*[^\n]");
Actually, you need to generate the format %19[, not %20[, so you
need ((sizeof s) - 1) here; and %d takes an int while sizeof
produces a size_t (perhaps a 64-bit unsigned long, vs the 32-bit
int).

This would work:

sprintf(tempfma t, "%%%d[^\n]%*[^\n]", (int)sizeof s - 1);


Not quite. You should now be able to see why I wrote "%s%d%s"
and a couple more string literals, rather than trying to squeeze
it all into the format string. That line *should* read

sprintf(tempfma t, "%%%d[^\n]%%*[^\n]", (int)sizeof s - 1);

to avoid undefined behavior. (Note the extra doubled % sign.)
And just to be double-triple-clc sure, we might as well use a
conversion specifier designed to handle unsigned values, in case
'sizeof s' doesn't fit in an 'int'. ;-)

sprintf(tempfma t, "%s%lu%s",
"%", (long unsigned)(sizeo f s - 1), "[^\n]%*[^\n]");
or you could even use the fact that it is "obvious" (?) that 20
and 19 are "the same number" :-) and just encode %19[ directly.


Also, looking more closely, you'll notice that 20 is not the
length of the buffer, but the length of the format string
'tempfmat'. The length of 's' is presumably not 20 -- I forget
what it was, but I'd guess 100 or 200. Which are just arbitrary
enough to make a stray %99s in a format string go undetected by
the next programmer. :)

Good catches on the off-by-one and size_t!

-Arthur
Nov 14 '05 #9
>On Wed, 21 Jan 2004, Chris Torek wrote:
This would work:
sprintf(tempfma t, "%%%d[^\n]%*[^\n]", (int)sizeof s - 1);

In article <news:Pi******* *************** ************@un ix46.andrew.cmu .edu>
Arthur J. O'Dwyer <aj*@nospam.and rew.cmu.edu> writes: Not quite. You should now be able to see why I wrote "%s%d%s"
and a couple more string literals, rather than trying to squeeze
it all into the format string. That line *should* read

sprintf(tempfma t, "%%%d[^\n]%%*[^\n]", (int)sizeof s - 1);

to avoid undefined behavior.
Oops. Indeed. (I did realize why you had used %s directives to
get the other directives through; I just wanted to point out that
-- with care -- they can be "inlined", as it were. But it takes
even more care than I used. :-) )
And just to be double-triple-clc sure, we might as well use a
conversion specifier designed to handle unsigned values, in case
'sizeof s' doesn't fit in an 'int'. ;-)


Although this will get the right value into the format even if
the "right value" is (say) 142599 even when INT_MAX is 32767,
it is not clear this would work "under the hood", as it were.
The width specifier is listed as a "decimal integer" (not
specifically an "int"), so it *might* work -- but the Standard
only requires relatively short and output lines to be supported
in the first place. (This might explain why %* directives in
printf take an int value instead of a size_t value. Either
that, or "no one thought about it", or "everyone figured it was
too ridiculous", or "it seemed necessary to keep it as int for
backwards compatibility", or some combination of these. :-) )

All in all, I still just prefer to avoid scanf(). :-)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
Nov 14 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

14
7665
by: jorntk | last post by:
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #define MAX 256 #define CMD_MAX 10 char *valid_cmds = " ls ps df "; int main (void) { char line_input, the_cmd; char *new_args, *cp;
11
5233
by: herrcho | last post by:
int get_lines(char *lines) { int n = 0; char buffer; puts("Enter one line at time; enter a blank when done"); while ((n < MAXLINES) && (gets(buffer) !=0 ) && (buffer != '\0')) { if ((lines = malloc(strlen(buffer)+1)) == NULL)
39
100764
by: Teh Charleh | last post by:
OK I have 2 similar programmes, why does the first one work and the second does not? Basically the problem is that the program seems to ignore the gets call if it comes after a scanf call. Please anything even a hint would be really helpful, I cant for the life of me see why the 2nd prog wont work... gets before scanf ...
52
3011
by: Christopher Benson-Manica | last post by:
gets() is universally acknowledged to be broken and useless; however, it is still part of the standard library. Why? Is there enough conforming code out there using gets() to justify retaining it? Are there plans to deprecate or eliminate it in a future version? -- Christopher Benson-Manica | I *should* know what I'm talking about - if...
57
11722
by: Eric Boutin | last post by:
Hi ! I was wondering how to quickly and safely use a safe scanf( ) or gets function... I mean.. if I do : char a; scanf("%s", a); and the user input a 257 char string.. that creates a problem.. same for gets.. even if you create a char array that's 99999999999999 char long.. if the user input something longer it will still be a...
9
5484
by: Sathyaish | last post by:
I noticed that gets() reads into the buffer even if the you've not allocated enough memory. For instance, if you do: char *str=(char*)malloc(sizeof(char)); printf("Enter something about yourself below:\n\n"); gets(str); printf("\n\n"); puts(str);
45
3418
by: Anthony Irwin | last post by:
Hi, I am fairly new to C and all the C books I got talk about gets() but when I compile it says I should not use gets() because it is dangerous. I understand that it is dangerous because it doesn't check whether there is more characters entered by the user the what can be stored but I don't know what the safe equivalent of gets() is. ...
20
10995
by: Xavoux | last post by:
Hello all... I can't remind which function to use for safe inputs... gets, fgets, scanf leads to buffer overflow... i compiled that code with gcc version 2.95.2, on windows 2000 char tmp0 = "ABCDEFGHI\0"; char buff; /* Input buffer. */ char tmp1 = "ABCDEFGHI\0";
280
8733
by: jacob navia | last post by:
In the discussion group comp.std.c Mr Gwyn wrote: < quote > .... gets has been declared an obsolescent feature and deprecated, as a direct result of my submitting a DR about it (which originally suggested a less drastic change). (The official impact awaits wrapping up the latest batch of TCs into a formal amending document, and getting...
0
7911
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main...
0
8338
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that...
1
7954
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For...
0
8215
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the...
1
5710
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes...
0
5390
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert...
0
3836
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in...
1
2345
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
0
1179
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.